diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /webkit | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'webkit')
376 files changed, 49510 insertions, 0 deletions
diff --git a/webkit/glue/DEPS b/webkit/glue/DEPS new file mode 100644 index 0000000..e59afec --- /dev/null +++ b/webkit/glue/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+app", + "+media", + "+skia/ext", + "+skia/include", + "+webkit/tools/test_shell", # Needed for test shell tests. + + # This is not actually a directory, but npruntime_util.cc includes a file + # from WebKit starting with this path in JSCore mode. + "+bindings/c", +] diff --git a/webkit/glue/alt_error_page_resource_fetcher.cc b/webkit/glue/alt_error_page_resource_fetcher.cc new file mode 100644 index 0000000..4e1867d --- /dev/null +++ b/webkit/glue/alt_error_page_resource_fetcher.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/alt_error_page_resource_fetcher.h" + +#include "base/callback.h" +#include "webkit/glue/resource_fetcher.h" + +using WebKit::WebFrame; +using WebKit::WebURLError; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +// Number of seconds to wait for the alternate error page server. If it takes +// too long, just use the local error page. +static const int kDownloadTimeoutSec = 3; + +AltErrorPageResourceFetcher::AltErrorPageResourceFetcher( + const GURL& url, + WebFrame* frame, + const WebURLError& original_error, + Callback* callback) + : frame_(frame), + callback_(callback), + original_error_(original_error) { + fetcher_.reset(new ResourceFetcherWithTimeout( + url, frame, kDownloadTimeoutSec, + NewCallback(this, &AltErrorPageResourceFetcher::OnURLFetchComplete))); +} + +AltErrorPageResourceFetcher::~AltErrorPageResourceFetcher() { +} + +void AltErrorPageResourceFetcher::Cancel() { + fetcher_->Cancel(); +} + +void AltErrorPageResourceFetcher::OnURLFetchComplete( + const WebURLResponse& response, + const std::string& data) { + // A null response indicates a network error. + if (!response.isNull() && response.httpStatusCode() == 200) { + callback_->Run(frame_, original_error_, data); + } else { + callback_->Run(frame_, original_error_, std::string()); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/alt_error_page_resource_fetcher.h b/webkit/glue/alt_error_page_resource_fetcher.h new file mode 100644 index 0000000..86671df --- /dev/null +++ b/webkit/glue/alt_error_page_resource_fetcher.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ +#define WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ + +#include "base/callback.h" +#include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" + +namespace WebKit { +class WebFrame; +class WebURLResponse; +} + +namespace webkit_glue { +class ResourceFetcherWithTimeout; + +// Used for downloading alternate dns error pages. Once downloading is done +// (or fails), the webview delegate is notified. +class AltErrorPageResourceFetcher { + public: + // This will be called when the alternative error page has been fetched, + // successfully or not. If there is a failure, the third parameter (the + // data) will be empty. + typedef Callback3<WebKit::WebFrame*, const WebKit::WebURLError&, + const std::string&>::Type Callback; + + AltErrorPageResourceFetcher(const GURL& url, + WebKit::WebFrame* frame, + const WebKit::WebURLError& original_error, + Callback* callback); + ~AltErrorPageResourceFetcher(); + + // Stop any pending loads. + void Cancel(); + + private: + void OnURLFetchComplete(const WebKit::WebURLResponse& response, + const std::string& data); + + // Does the actual fetching. + scoped_ptr<ResourceFetcherWithTimeout> fetcher_; + + WebKit::WebFrame* frame_; + scoped_ptr<Callback> callback_; + + // The error associated with this load. If there's an error talking with the + // alt error page server, we need this to complete the original load. + WebKit::WebURLError original_error_; + + DISALLOW_COPY_AND_ASSIGN(AltErrorPageResourceFetcher); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_ALT_ERROR_PAGE_RESOURCE_FETCHER_H_ diff --git a/webkit/glue/bookmarklet_unittest.cc b/webkit/glue/bookmarklet_unittest.cc new file mode 100644 index 0000000..5d8a364 --- /dev/null +++ b/webkit/glue/bookmarklet_unittest.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2006-2008 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/file_util.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +namespace { + +class BookmarkletTest : public TestShellTest { + public: + virtual void SetUp() { + TestShellTest::SetUp(); + + test_shell_->LoadURL(GURL("data:text/html,start page")); + test_shell_->WaitTestFinished(); + } +}; + +TEST_F(BookmarkletTest, Redirect) { + test_shell_->LoadURL( + GURL("javascript:location.href='data:text/plain,SUCCESS'")); + test_shell_->WaitTestFinished(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"SUCCESS", text); +} + +TEST_F(BookmarkletTest, RedirectVoided) { + // This test should be redundant with the Redirect test above. The point + // here is to emphasize that in either case the assignment to location during + // the evaluation of the script should suppress loading the script result. + // Here, because of the void() wrapping there is no script result. + test_shell_->LoadURL( + GURL("javascript:void(location.href='data:text/plain,SUCCESS')")); + test_shell_->WaitTestFinished(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"SUCCESS", text); +} + +TEST_F(BookmarkletTest, NonEmptyResult) { + std::wstring text; + + // TODO(darin): This test fails in a JSC build. WebCore+JSC does not really + // need to support this usage until WebCore supports javascript: URLs that + // generate content (https://bugs.webkit.org/show_bug.cgi?id=14959). It is + // important to note that Safari does not support bookmarklets, and this is + // really an edge case. Our behavior with V8 is consistent with FF and IE. +#if 0 + test_shell_->LoadURL(L"javascript:false"); + MessageLoop::current()->RunAllPending(); + text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"false", text); +#endif + + test_shell_->LoadURL(GURL("javascript:'hello world'")); + MessageLoop::current()->RunAllPending(); + text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"hello world", text); +} + +TEST_F(BookmarkletTest, DocumentWrite) { + test_shell_->LoadURL(GURL( + "javascript:document.open();" + "document.write('hello world');" + "document.close()")); + MessageLoop::current()->RunAllPending(); + std::wstring text = test_shell_->GetDocumentText(); + EXPECT_EQ(L"hello world", text); +} + +} // namespace diff --git a/webkit/glue/context_menu.h b/webkit/glue/context_menu.h new file mode 100644 index 0000000..764fb9d --- /dev/null +++ b/webkit/glue/context_menu.h @@ -0,0 +1,129 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_CONTEXT_MENU_H_ +#define WEBKIT_GLUE_CONTEXT_MENU_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "webkit/glue/webmenuitem.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebContextMenuData.h" + +// Parameters structure for ViewHostMsg_ContextMenu. +// FIXME(beng): This would be more useful in the future and more efficient +// if the parameters here weren't so literally mapped to what +// they contain for the ContextMenu task. It might be better +// to make the string fields more generic so that this object +// could be used for more contextual actions. +struct ContextMenuParams { + // This is the type of Context Node that the context menu was invoked on. + WebKit::WebContextMenuData::MediaType media_type; + + // These values represent the coordinates of the mouse when the context menu + // was invoked. Coords are relative to the associated RenderView's origin. + int x; + int y; + + // This is the URL of the link that encloses the node the context menu was + // invoked on. + GURL link_url; + + // The link URL to be used ONLY for "copy link address". We don't validate + // this field in the frontend process. + GURL unfiltered_link_url; + + // This is the source URL for the element that the context menu was + // invoked on. Example of elements with source URLs are img, audio, and + // video. + GURL src_url; + + // This is true if the context menu was invoked on a blocked image. + bool is_image_blocked; + + // This is the URL of the top level page that the context menu was invoked + // on. + GURL page_url; + + // This is the URL of the subframe that the context menu was invoked on. + GURL frame_url; + + // These are the parameters for the media element that the context menu + // was invoked on. + int media_flags; + + // This is the text of the selection that the context menu was invoked on. + std::wstring selection_text; + + // The misspelled word under the cursor, if any. Used to generate the + // |dictionary_suggestions| list. + string16 misspelled_word; + + // Suggested replacements for a misspelled word under the cursor. + // This vector gets populated in the render process host + // by intercepting ViewHostMsg_ContextMenu in ResourceMessageFilter + // and populating dictionary_suggestions if the type is EDITABLE + // and the misspelled_word is not empty. + std::vector<string16> dictionary_suggestions; + + // If editable, flag for whether spell check is enabled or not. + bool spellcheck_enabled; + + // Whether context is editable. + bool is_editable; + +#if defined(OS_MACOSX) + // Writing direction menu items. + // Currently only used on OS X. + int writing_direction_default; + int writing_direction_left_to_right; + int writing_direction_right_to_left; +#endif // OS_MACOSX + + // These flags indicate to the browser whether the renderer believes it is + // able to perform the corresponding action. + int edit_flags; + + // The security info for the resource we are showing the menu on. + std::string security_info; + + // The character encoding of the frame on which the menu is invoked. + std::string frame_charset; + + std::vector<WebMenuItem> custom_items; + + ContextMenuParams() {} + + ContextMenuParams(const WebKit::WebContextMenuData& data) + : media_type(data.mediaType), + x(data.mousePosition.x), + y(data.mousePosition.y), + link_url(data.linkURL), + unfiltered_link_url(data.linkURL), + src_url(data.srcURL), + is_image_blocked(data.isImageBlocked), + page_url(data.pageURL), + frame_url(data.frameURL), + media_flags(data.mediaFlags), + selection_text(UTF16ToWideHack(data.selectedText)), + misspelled_word(data.misspelledWord), + spellcheck_enabled(data.isSpellCheckingEnabled), + is_editable(data.isEditable), +#if defined(OS_MACOSX) + writing_direction_default(data.writingDirectionDefault), + writing_direction_left_to_right(data.writingDirectionLeftToRight), + writing_direction_right_to_left(data.writingDirectionRightToLeft), +#endif // OS_MACOSX + edit_flags(data.editFlags), + security_info(data.securityInfo), + frame_charset(data.frameEncoding.utf8()) { + for (size_t i = 0; i < data.customItems.size(); ++i) + custom_items.push_back(WebMenuItem(data.customItems[i])); + } +}; + +#endif // WEBKIT_GLUE_CONTEXT_MENU_H_ diff --git a/webkit/glue/context_menu_unittest.cc b/webkit/glue/context_menu_unittest.cc new file mode 100644 index 0000000..9cefc9f --- /dev/null +++ b/webkit/glue/context_menu_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 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. + +// Tests for displaying context menus in corner cases (and swallowing context +// menu events when appropriate) + +#include <vector> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" +#include "webkit/tools/test_shell/test_webview_delegate.h" + +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebView; + +// Right clicking inside on an iframe should produce a context menu +class ContextMenuCapturing : public TestShellTest { + protected: + void SetUp() { + TestShellTest::SetUp(); + + iframes_data_dir_ = data_dir_; + iframes_data_dir_ = iframes_data_dir_.AppendASCII("test_shell"); + iframes_data_dir_ = iframes_data_dir_.AppendASCII("iframes"); + ASSERT_TRUE(file_util::PathExists(iframes_data_dir_)); + } + + FilePath iframes_data_dir_; +}; + + +TEST_F(ContextMenuCapturing, ContextMenuCapturing) { + // Make sure we have no stored mouse event state + TestWebViewDelegate* test_delegate = test_shell_->delegate(); + test_delegate->clear_captured_context_menu_events(); + EXPECT_EQ(0U, test_delegate->captured_context_menu_events().size()); + + GURL test_url = GetTestURL(iframes_data_dir_, "testiframe.html"); + test_shell_->LoadURL(test_url); + test_shell_->WaitTestFinished(); + + // Create a right click in the center of the iframe. (I'm hoping this will + // make this a bit more robust in case of some other formatting or other bug.) + WebMouseEvent mouse_event; + mouse_event.type = WebInputEvent::MouseDown; + mouse_event.button = WebMouseEvent::ButtonRight; + mouse_event.x = 250; + mouse_event.y = 250; + mouse_event.globalX = 250; + mouse_event.globalY = 250; + + WebView* webview = test_shell_->webView(); + webview->handleInputEvent(mouse_event); + + // Now simulate the corresponding up event which should display the menu + mouse_event.type = WebInputEvent::MouseUp; + webview->handleInputEvent(mouse_event); + + EXPECT_EQ(1U, test_delegate->captured_context_menu_events().size()); +} diff --git a/webkit/glue/cpp_binding_example.cc b/webkit/glue/cpp_binding_example.cc new file mode 100644 index 0000000..79e96e4 --- /dev/null +++ b/webkit/glue/cpp_binding_example.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2006-2008 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. + +// This file contains the definition for CppBindingExample, which is used in +// cpp_bound_class_unittest. + +#include "cpp_binding_example.h" + +namespace { + +class PropertyCallbackExample : public CppBoundClass::PropertyCallback { + public: + virtual bool GetValue(CppVariant* value) { + value->Set(value_); + return true; + } + + virtual bool SetValue(const CppVariant& value) { + value_.Set(value); + return true; + } + + private: + CppVariant value_; +}; + +} + +CppBindingExample::CppBindingExample() { + // Map properties. It's recommended, but not required, that the JavaScript + // names (used as the keys in this map) match the names of the member + // variables exposed through those names. + BindProperty("my_value", &my_value); + BindProperty("my_other_value", &my_other_value); + + // Bind property with a callback. + BindProperty("my_value_with_callback", new PropertyCallbackExample()); + // Bind property with a getter callback. + BindProperty("same", &CppBindingExample::same); + + // Map methods. See comment above about names. + BindMethod("echoValue", &CppBindingExample::echoValue); + BindMethod("echoType", &CppBindingExample::echoType); + BindMethod("plus", &CppBindingExample::plus); + + // The fallback method is called when a nonexistent method is called on an + // object. If none is specified, calling a nonexistent method causes an + // exception to be thrown and the JavaScript execution is stopped. + BindFallbackMethod(&CppBindingExample::fallbackMethod); + + my_value.Set(10); + my_other_value.Set("Reinitialized!"); +} + +void CppBindingExample::echoValue(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 1) { + result->SetNull(); + return; + } + result->Set(args[0]); +} + +void CppBindingExample::echoType(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 1) { + result->SetNull(); + return; + } + // Note that if args[0] is a string, the following assignment implicitly + // makes a copy of that string, which may have an undesirable impact on + // performance. + CppVariant arg1 = args[0]; + if (arg1.isBool()) + result->Set(true); + else if (arg1.isInt32()) + result->Set(7); + else if (arg1.isDouble()) + result->Set(3.14159); + else if (arg1.isString()) + result->Set("Success!"); +} + +void CppBindingExample::plus(const CppArgumentList& args, + CppVariant* result) { + if (args.size() < 2) { + result->SetNull(); + return; + } + + CppVariant arg1 = args[0]; + CppVariant arg2 = args[1]; + + if (!arg1.isNumber() || !arg2.isNumber()) { + result->SetNull(); + return; + } + + // The value of a CppVariant may be read directly from its NPVariant struct. + // (However, it should only be set using one of the Set() functions.) + double sum = 0.; + if (arg1.isDouble()) + sum += arg1.value.doubleValue; + else if (arg1.isInt32()) + sum += arg1.value.intValue; + + if (arg2.isDouble()) + sum += arg2.value.doubleValue; + else if (arg2.isInt32()) + sum += arg2.value.intValue; + + result->Set(sum); +} + +void CppBindingExample::same(CppVariant* result) { + result->Set(42); +} + +void CppBindingExample::fallbackMethod(const CppArgumentList& args, + CppVariant* result) { + printf("Error: unknown JavaScript method invoked.\n"); +} diff --git a/webkit/glue/cpp_binding_example.h b/webkit/glue/cpp_binding_example.h new file mode 100644 index 0000000..a663682 --- /dev/null +++ b/webkit/glue/cpp_binding_example.h @@ -0,0 +1,78 @@ +// Copyright (c) 2006-2008 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. + +/* + CppBindingExample class: + This provides an example of how to use the CppBoundClass to create methods + and properties that can be exposed to JavaScript by an appropriately built + embedding client. It is also used by the CppBoundClass unit test. + + Typically, a class intended to be bound to JavaScript will define a + constructor, any methods and properties to be exposed, and optionally a + destructor. An embedding client can then bind the class to a JavaScript + object in a frame's window using the CppBoundClass::BindToJavascript() method, + generally called from the WebView delegate's WindowObjectCleared(). + + Once this class has been bound, say to the name "example", it might be called + from JavaScript in the following way: + + <script> + if (window.example) { + document.writeln(example.echoValue(false)); + document.writeln(example.echoType("Hello world!")); + document.writeln(example.plus(2, 3.1)); + + example.my_value = 15; + example.my_other_value = 2.1; + document.writeln(example.plus(example.my_value, example.my_other_value)); + } + </script> +*/ + +#ifndef CPP_BINDING_EXAMPLE_H__ +#define CPP_BINDING_EXAMPLE_H__ + +#include "webkit/glue/cpp_bound_class.h" + +class CppBindingExample : public CppBoundClass { + public: + // The default constructor initializes the property and method lists needed + // to bind this class to a JS object. + CppBindingExample(); + + // + // These public member variables and methods implement the methods and + // properties that will be exposed to JavaScript. If needed, the class could + // also contain other methods or variables, which will be hidden from JS + // as long as they're not mapped in the property and method lists created in + // the constructor. + // + // The signatures of any methods to be bound must match + // CppBoundClass::Callback. + // + + // Returns the value that was passed in as its first (only) argument. + void echoValue(const CppArgumentList& args, CppVariant* result); + + // Returns a hard-coded value of the same type (bool, number (double), + // string, or null) that was passed in as an argument. + void echoType(const CppArgumentList& args, CppVariant* result); + + // Returns the sum of the (first) two arguments as a double, if they are both + // numbers (integers or doubles). Otherwise returns null. + void plus(const CppArgumentList& args, CppVariant* result); + + // Always returns the same value -- an example of a read-only property. + void same(CppVariant* result); + + // Invoked when a nonexistent method is called on this example object, this + // prints an error message. + void fallbackMethod(const CppArgumentList& args, CppVariant* result); + + // These properties will also be exposed to JavaScript. + CppVariant my_value; + CppVariant my_other_value; +}; + +#endif // CPP_BINDING_EXAMPLE_H__ diff --git a/webkit/glue/cpp_bound_class.cc b/webkit/glue/cpp_bound_class.cc new file mode 100644 index 0000000..09c3f40 --- /dev/null +++ b/webkit/glue/cpp_bound_class.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2006-2008 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. + +// This file contains definitions for CppBoundClass + +// Here's the control flow of a JS method getting forwarded to a class. +// - Something calls our NPObject with a function like "Invoke". +// - CppNPObject's static invoke() function forwards it to its attached +// CppBoundClass's Invoke() method. +// - CppBoundClass has then overridden Invoke() to look up the function +// name in its internal map of methods, and then calls the appropriate +// method. + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "webkit/glue/cpp_bound_class.h" + +using WebKit::WebBindings; +using WebKit::WebFrame; + +namespace { + +class CppVariantPropertyCallback : public CppBoundClass::PropertyCallback { + public: + CppVariantPropertyCallback(CppVariant* value) : value_(value) { } + + virtual bool GetValue(CppVariant* value) { + value->Set(*value_); + return true; + } + virtual bool SetValue(const CppVariant& value) { + value_->Set(value); + return true; + } + + private: + CppVariant* value_; +}; + +class GetterPropertyCallback : public CppBoundClass::PropertyCallback { +public: + GetterPropertyCallback(CppBoundClass::GetterCallback* callback) + : callback_(callback) { } + + virtual bool GetValue(CppVariant* value) { + callback_->Run(value); + return true; + } + + virtual bool SetValue(const CppVariant& value) { + return false; + } + +private: + scoped_ptr<CppBoundClass::GetterCallback> callback_; +}; + +} + +// Our special NPObject type. We extend an NPObject with a pointer to a +// CppBoundClass, which is just a C++ interface that we forward all NPObject +// callbacks to. +struct CppNPObject { + NPObject parent; // This must be the first field in the struct. + CppBoundClass* bound_class; + + // + // All following objects and functions are static, and just used to interface + // with NPObject/NPClass. + // + + // An NPClass associates static functions of CppNPObject with the + // function pointers used by the JS runtime. + static NPClass np_class_; + + // Allocate a new NPObject with the specified class. + static NPObject* allocate(NPP npp, NPClass* aClass); + + // Free an object. + static void deallocate(NPObject* obj); + + // Returns true if the C++ class associated with this NPObject exposes the + // given property. Called by the JS runtime. + static bool hasProperty(NPObject *obj, NPIdentifier ident); + + // Returns true if the C++ class associated with this NPObject exposes the + // given method. Called by the JS runtime. + static bool hasMethod(NPObject *obj, NPIdentifier ident); + + // If the given method is exposed by the C++ class associated with this + // NPObject, invokes it with the given args and returns a result. Otherwise, + // returns "undefined" (in the JavaScript sense). Called by the JS runtime. + static bool invoke(NPObject *obj, NPIdentifier ident, + const NPVariant *args, uint32_t arg_count, + NPVariant *result); + + // If the given property is exposed by the C++ class associated with this + // NPObject, returns its value. Otherwise, returns "undefined" (in the + // JavaScript sense). Called by the JS runtime. + static bool getProperty(NPObject *obj, NPIdentifier ident, + NPVariant *result); + + // If the given property is exposed by the C++ class associated with this + // NPObject, sets its value. Otherwise, does nothing. Called by the JS + // runtime. + static bool setProperty(NPObject *obj, NPIdentifier ident, + const NPVariant *value); +}; + +// Build CppNPObject's static function pointers into an NPClass, for use +// in constructing NPObjects for the C++ classes. +NPClass CppNPObject::np_class_ = { + NP_CLASS_STRUCT_VERSION, + CppNPObject::allocate, + CppNPObject::deallocate, + /* NPInvalidateFunctionPtr */ NULL, + CppNPObject::hasMethod, + CppNPObject::invoke, + /* NPInvokeDefaultFunctionPtr */ NULL, + CppNPObject::hasProperty, + CppNPObject::getProperty, + CppNPObject::setProperty, + /* NPRemovePropertyFunctionPtr */ NULL +}; + +/* static */ NPObject* CppNPObject::allocate(NPP npp, NPClass* aClass) { + CppNPObject* obj = new CppNPObject; + // obj->parent will be initialized by the NPObject code calling this. + obj->bound_class = NULL; + return &obj->parent; +} + +/* static */ void CppNPObject::deallocate(NPObject* np_obj) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + delete obj; +} + +/* static */ bool CppNPObject::hasMethod(NPObject* np_obj, + NPIdentifier ident) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + return obj->bound_class->HasMethod(ident); +} + +/* static */ bool CppNPObject::hasProperty(NPObject* np_obj, + NPIdentifier ident) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + return obj->bound_class->HasProperty(ident); +} + +/* static */ bool CppNPObject::invoke(NPObject* np_obj, NPIdentifier ident, + const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + return obj->bound_class->Invoke(ident, args, arg_count, result); +} + +/* static */ bool CppNPObject::getProperty(NPObject* np_obj, + NPIdentifier ident, + NPVariant* result) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + return obj->bound_class->GetProperty(ident, result); +} + +/* static */ bool CppNPObject::setProperty(NPObject* np_obj, + NPIdentifier ident, + const NPVariant* value) { + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + return obj->bound_class->SetProperty(ident, value); +} + +CppBoundClass::~CppBoundClass() { + for (MethodList::iterator i = methods_.begin(); i != methods_.end(); ++i) + delete i->second; + + for (PropertyList::iterator i = properties_.begin(); i != properties_.end(); + ++i) { + delete i->second; + } + + // Unregister ourselves if we were bound to a frame. + if (bound_to_frame_) + WebBindings::unregisterObject(NPVARIANT_TO_OBJECT(self_variant_)); +} + +bool CppBoundClass::HasMethod(NPIdentifier ident) const { + return (methods_.find(ident) != methods_.end()); +} + +bool CppBoundClass::HasProperty(NPIdentifier ident) const { + return (properties_.find(ident) != properties_.end()); +} + +bool CppBoundClass::Invoke(NPIdentifier ident, + const NPVariant* args, + size_t arg_count, + NPVariant* result) { + MethodList::const_iterator method = methods_.find(ident); + Callback* callback; + if (method == methods_.end()) { + if (fallback_callback_.get()) { + callback = fallback_callback_.get(); + } else { + VOID_TO_NPVARIANT(*result); + return false; + } + } else { + callback = (*method).second; + } + + // Build a CppArgumentList argument vector from the NPVariants coming in. + CppArgumentList cpp_args(arg_count); + for (size_t i = 0; i < arg_count; i++) + cpp_args[i].Set(args[i]); + + CppVariant cpp_result; + callback->Run(cpp_args, &cpp_result); + + cpp_result.CopyToNPVariant(result); + return true; +} + +bool CppBoundClass::GetProperty(NPIdentifier ident, NPVariant* result) const { + PropertyList::const_iterator callback = properties_.find(ident); + if (callback == properties_.end()) { + VOID_TO_NPVARIANT(*result); + return false; + } + + CppVariant cpp_value; + if (!callback->second->GetValue(&cpp_value)) + return false; + cpp_value.CopyToNPVariant(result); + return true; +} + +bool CppBoundClass::SetProperty(NPIdentifier ident, + const NPVariant* value) { + PropertyList::iterator callback = properties_.find(ident); + if (callback == properties_.end()) + return false; + + CppVariant cpp_value; + cpp_value.Set(*value); + return (*callback).second->SetValue(cpp_value); +} + +void CppBoundClass::BindCallback(const std::string& name, Callback* callback) { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + MethodList::iterator old_callback = methods_.find(ident); + if (old_callback != methods_.end()) { + delete old_callback->second; + if (callback == NULL) { + methods_.erase(old_callback); + return; + } + } + + methods_[ident] = callback; +} + +void CppBoundClass::BindGetterCallback(const std::string& name, + GetterCallback* callback) { + PropertyCallback* property_callback = callback == NULL ? + NULL : new GetterPropertyCallback(callback); + + BindProperty(name, property_callback); +} + +void CppBoundClass::BindProperty(const std::string& name, CppVariant* prop) { + PropertyCallback* property_callback = prop == NULL ? + NULL : new CppVariantPropertyCallback(prop); + + BindProperty(name, property_callback); +} + +void CppBoundClass::BindProperty(const std::string& name, + PropertyCallback* callback) { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + PropertyList::iterator old_callback = properties_.find(ident); + if (old_callback != properties_.end()) { + delete old_callback->second; + if (callback == NULL) { + properties_.erase(old_callback); + return; + } + } + + properties_[ident] = callback; +} + +bool CppBoundClass::IsMethodRegistered(const std::string& name) const { + NPIdentifier ident = WebBindings::getStringIdentifier(name.c_str()); + MethodList::const_iterator callback = methods_.find(ident); + return (callback != methods_.end()); +} + +CppVariant* CppBoundClass::GetAsCppVariant() { + if (!self_variant_.isObject()) { + // Create an NPObject using our static NPClass. The first argument (a + // plugin's instance handle) is passed through to the allocate function + // directly, and we don't use it, so it's ok to be 0. + NPObject* np_obj = WebBindings::createObject(0, &CppNPObject::np_class_); + CppNPObject* obj = reinterpret_cast<CppNPObject*>(np_obj); + obj->bound_class = this; + self_variant_.Set(np_obj); + WebBindings::releaseObject(np_obj); // CppVariant takes the reference. + } + DCHECK(self_variant_.isObject()); + return &self_variant_; +} + +void CppBoundClass::BindToJavascript(WebFrame* frame, + const std::wstring& classname) { +#if WEBKIT_USING_JSC +#error "This is not going to work anymore...but it's not clear what the solution is...or if it's still necessary." + JSC::JSLock lock(false); +#endif + + // BindToWindowObject will take its own reference to the NPObject, and clean + // up after itself. It will also (indirectly) register the object with V8, + // so we must remember this so we can unregister it when we're destroyed. + frame->bindToWindowObject(WideToUTF16Hack(classname), + NPVARIANT_TO_OBJECT(*GetAsCppVariant())); + bound_to_frame_ = true; +} diff --git a/webkit/glue/cpp_bound_class.h b/webkit/glue/cpp_bound_class.h new file mode 100644 index 0000000..dcd5c3e --- /dev/null +++ b/webkit/glue/cpp_bound_class.h @@ -0,0 +1,181 @@ +// Copyright (c) 2010 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. + +/* + CppBoundClass class: + This base class serves as a parent for C++ classes designed to be bound to + JavaScript objects. + + Subclasses should define the constructor to build the property and method + lists needed to bind this class to a JS object. They should also declare + and define member variables and methods to be exposed to JS through + that object. + + See cpp_binding_example.{h|cc} for an example. +*/ + +#ifndef WEBKIT_GLUE_CPP_BOUNDCLASS_H__ +#define WEBKIT_GLUE_CPP_BOUNDCLASS_H__ + +#include <map> +#include <vector> + +#include "webkit/glue/cpp_variant.h" + +#include "base/callback.h" +#include "base/scoped_ptr.h" + +namespace WebKit { +class WebFrame; +} + +typedef std::vector<CppVariant> CppArgumentList; + +// CppBoundClass lets you map Javascript method calls and property accesses +// directly to C++ method calls and CppVariant* variable access. +class CppBoundClass { + public: + class PropertyCallback { + public: + virtual ~PropertyCallback() { } + + // Sets |value| to the value of the property. Returns false in case of + // failure. |value| is always non-NULL. + virtual bool GetValue(CppVariant* value) = 0; + + // sets the property value to |value|. Returns false in case of failure. + virtual bool SetValue(const CppVariant& value) = 0; + }; + + // The constructor should call BindMethod, BindProperty, and + // SetFallbackMethod as needed to set up the methods, properties, and + // fallback method. + CppBoundClass() : bound_to_frame_(false) { } + virtual ~CppBoundClass(); + + // Return a CppVariant representing this class, for use with BindProperty(). + // The variant type is guaranteed to be NPVariantType_Object. + CppVariant* GetAsCppVariant(); + + // Given a WebFrame, BindToJavascript builds the NPObject that will represent + // the class and binds it to the frame's window under the given name. This + // should generally be called from the WebView delegate's + // WindowObjectCleared(). A class so bound will be accessible to JavaScript + // as window.<classname>. The owner of the CppBoundObject is responsible for + // keeping the object around while the frame is alive, and for destroying it + // afterwards. + void BindToJavascript( + WebKit::WebFrame* frame, const std::wstring& classname); + + // The type of callbacks. + typedef Callback2<const CppArgumentList&, CppVariant*>::Type Callback; + typedef Callback1<CppVariant*>::Type GetterCallback; + + // Used by a test. Returns true if a method with name |name| exists, + // regardless of whether a fallback is registered. + bool IsMethodRegistered(const std::string& name) const; + + protected: + // Bind the Javascript method called |name| to the C++ callback |callback|. + void BindCallback(const std::string& name, Callback* callback); + + // A wrapper for BindCallback, to simplify the common case of binding a + // method on the current object. Though not verified here, |method| + // must be a method of this CppBoundClass subclass. + template<typename T> + void BindMethod(const std::string& name, + void (T::*method)(const CppArgumentList&, CppVariant*)) { + Callback* callback = + NewCallback<T, const CppArgumentList&, CppVariant*>( + static_cast<T*>(this), method); + BindCallback(name, callback); + } + + // Bind Javascript property |name| to the C++ getter callback |callback|. + // This can be used to create read-only properties. + void BindGetterCallback(const std::string& name, GetterCallback* callback); + + // A wrapper for BindGetterCallback, to simplify the common case of binding a + // property on the current object. Though not verified here, |method| + // must be a method of this CppBoundClass subclass. + template<typename T> + void BindProperty(const std::string& name, void (T::*method)(CppVariant*)) { + GetterCallback* callback = + NewCallback<T, CppVariant*>(static_cast<T*>(this), method); + BindGetterCallback(name, callback); + } + + // Bind the Javascript property called |name| to a CppVariant |prop|. + void BindProperty(const std::string& name, CppVariant* prop); + + // Bind Javascript property called |name| to a PropertyCallback |callback|. + // CppBoundClass assumes control over the life time of the |callback|. + void BindProperty(const std::string& name, PropertyCallback* callback); + + // Set the fallback callback, which is called when when a callback is + // invoked that isn't bound. + // If it is NULL (its default value), a JavaScript exception is thrown in + // that case (as normally expected). If non NULL, the fallback method is + // invoked and the script continues its execution. + // Passing NULL for |callback| clears out any existing binding. + // It is used for tests and should probably only be used in such cases + // as it may cause unexpected behaviors (a JavaScript object with a + // fallback always returns true when checked for a method's + // existence). + void BindFallbackCallback(Callback* fallback_callback) { + fallback_callback_.reset(fallback_callback); + } + + // A wrapper for BindFallbackCallback, to simplify the common case of + // binding a method on the current object. Though not verified here, + // |method| must be a method of this CppBoundClass subclass. + // Passing NULL for |method| clears out any existing binding. + template<typename T> + void BindFallbackMethod( + void (T::*method)(const CppArgumentList&, CppVariant*)) { + if (method) { + Callback* callback = + NewCallback<T, const CppArgumentList&, CppVariant*>( + static_cast<T*>(this), method); + BindFallbackCallback(callback); + } else { + BindFallbackCallback(NULL); + } + } + + // Some fields are protected because some tests depend on accessing them, + // but otherwise they should be considered private. + + typedef std::map<NPIdentifier, PropertyCallback*> PropertyList; + typedef std::map<NPIdentifier, Callback*> MethodList; + // These maps associate names with property and method pointers to be + // exposed to JavaScript. + PropertyList properties_; + MethodList methods_; + + // The callback gets invoked when a call is made to an nonexistent method. + scoped_ptr<Callback> fallback_callback_; + + private: + // NPObject callbacks. + friend struct CppNPObject; + bool HasMethod(NPIdentifier ident) const; + bool Invoke(NPIdentifier ident, const NPVariant* args, size_t arg_count, + NPVariant* result); + bool HasProperty(NPIdentifier ident) const; + bool GetProperty(NPIdentifier ident, NPVariant* result) const; + bool SetProperty(NPIdentifier ident, const NPVariant* value); + + // A lazily-initialized CppVariant representing this class. We retain 1 + // reference to this object, and it is released on deletion. + CppVariant self_variant_; + + // True if our np_object has been bound to a WebFrame, in which case it must + // be unregistered with V8 when we delete it. + bool bound_to_frame_; + + DISALLOW_COPY_AND_ASSIGN(CppBoundClass); +}; + +#endif // CPP_BOUNDCLASS_H__ diff --git a/webkit/glue/cpp_bound_class_unittest.cc b/webkit/glue/cpp_bound_class_unittest.cc new file mode 100644 index 0000000..71a56e6 --- /dev/null +++ b/webkit/glue/cpp_bound_class_unittest.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2006-2008 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. + +// Tests for CppBoundClass, in conjunction with CppBindingExample. Binds +// a CppBindingExample class into JavaScript in a custom test shell and tests +// the binding from the outside by loading JS into the shell. + +#include <vector> + +#include "base/message_loop.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/cpp_binding_example.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebFrame; + +namespace { + +class CppBindingExampleSubObject : public CppBindingExample { + public: + CppBindingExampleSubObject() { + sub_value_.Set("sub!"); + BindProperty("sub_value", &sub_value_); + } + private: + CppVariant sub_value_; +}; + + +class CppBindingExampleWithOptionalFallback : public CppBindingExample { + public: + CppBindingExampleWithOptionalFallback() { + BindProperty("sub_object", sub_object_.GetAsCppVariant()); + } + + void set_fallback_method_enabled(bool state) { + BindFallbackMethod(state ? + &CppBindingExampleWithOptionalFallback::fallbackMethod + : NULL); + } + + // The fallback method does nothing, but because of it the JavaScript keeps + // running when a nonexistent method is called on an object. + void fallbackMethod(const CppArgumentList& args, CppVariant* result) { + } + + private: + CppBindingExampleSubObject sub_object_; +}; + +class ExampleTestShell : public TestShell { + public: + + ExampleTestShell(bool use_fallback_method) { + example_bound_class_.set_fallback_method_enabled(use_fallback_method); + } + + // When called by WebViewDelegate::WindowObjectCleared method, this binds a + // CppExampleObject to window.example. + virtual void BindJSObjectsToWindow(WebFrame* frame) { + example_bound_class_.BindToJavascript(frame, L"example"); + // We use the layoutTestController binding for notifyDone. + TestShell::BindJSObjectsToWindow(frame); + } + + // This is a public interface to TestShell's protected method, so it + // can be called by our CreateEmptyWindow. + bool PublicInitialize(const std::string& starting_url) { + return Initialize(GURL(starting_url)); + } + + CppBindingExampleWithOptionalFallback example_bound_class_; +}; + +class CppBoundClassTest : public TestShellTest { + protected: + // Adapted from TestShell::CreateNewWindow, this creates an + // ExampleTestShellWindow rather than a regular TestShell. + virtual void CreateEmptyWindow() { + ExampleTestShell* host = new ExampleTestShell(useFallback()); + ASSERT_TRUE(host != NULL); + bool rv = host->PublicInitialize("about:blank"); + if (rv) { + test_shell_ = host; + TestShell::windowList()->push_back(host->mainWnd()); + webframe_ = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(webframe_ != NULL); + } else { + delete host; + } + } + + // Wraps the given JavaScript snippet in <html><body><script> tags, then + // loads it into a webframe so it is executed. + void ExecuteJavaScript(const std::string& javascript) { + std::string html = "<html><body>"; + html.append(TestShellTest::kJavascriptDelayExitScript); + html.append("<script>"); + html.append(javascript); + html.append("</script></body></html>"); + // The base URL doesn't matter. + webframe_->loadHTMLString(html, GURL("about:blank")); + + test_shell_->WaitTestFinished(); + } + + // Executes the specified JavaScript and checks to be sure that the resulting + // document text is exactly "SUCCESS". + void CheckJavaScriptSuccess(const std::string& javascript) { + ExecuteJavaScript(javascript); + EXPECT_EQ(L"SUCCESS", webkit_glue::DumpDocumentText(webframe_)); + } + + // Executes the specified JavaScript and checks that the resulting document + // text is empty. + void CheckJavaScriptFailure(const std::string& javascript) { + ExecuteJavaScript(javascript); + EXPECT_EQ(L"", webkit_glue::DumpDocumentText(webframe_)); + } + + // Constructs a JavaScript snippet that evaluates and compares the left and + // right expressions, printing "SUCCESS" to the page if they are equal and + // printing their actual values if they are not. Any strings in the + // expressions should be enclosed in single quotes, and no double quotes + // should appear in either expression (even if escaped). (If a test case + // is added that needs fancier quoting, Json::valueToQuotedString could be + // used here. For now, it's not worth adding the dependency.) + std::string BuildJSCondition(std::string left, std::string right) { + return "var leftval = " + left + ";" + + "var rightval = " + right + ";" + + "if (leftval == rightval) {" + + " document.writeln('SUCCESS');" + + "} else {" + + " document.writeln(\"" + + left + " [\" + leftval + \"] != " + + right + " [\" + rightval + \"]\");" + + "}"; + } + +protected: + virtual bool useFallback() { + return false; + } + +private: + WebFrame* webframe_; +}; + +class CppBoundClassWithFallbackMethodTest : public CppBoundClassTest { +protected: + virtual bool useFallback() { + return true; + } +}; + +// Ensures that the example object has been bound to JS. +TEST_F(CppBoundClassTest, ObjectExists) { + std::string js = BuildJSCondition("typeof window.example", "'object'"); + CheckJavaScriptSuccess(js); + + // An additional check to test our test. + js = BuildJSCondition("typeof window.invalid_object", "'undefined'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, PropertiesAreInitialized) { + std::string js = BuildJSCondition("example.my_value", "10"); + CheckJavaScriptSuccess(js); + + js = BuildJSCondition("example.my_other_value", "'Reinitialized!'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, SubOject) { + std::string js = BuildJSCondition("typeof window.example.sub_object", + "'object'"); + CheckJavaScriptSuccess(js); + + js = BuildJSCondition("example.sub_object.sub_value", "'sub!'"); + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, SetAndGetProperties) { + // The property on the left will be set to the value on the right, then + // checked to make sure it holds that same value. + static const std::string tests[] = { + "example.my_value", "7", + "example.my_value", "'test'", + "example.my_other_value", "3.14", + "example.my_other_value", "false", + "" // Array end marker: insert additional test pairs before this. + }; + + for (int i = 0; tests[i] != ""; i += 2) { + std::string left = tests[i]; + std::string right = tests[i + 1]; + // left = right; + std::string js = left; + js.append(" = "); + js.append(right); + js.append(";"); + js.append(BuildJSCondition(left, right)); + CheckJavaScriptSuccess(js); + } +} + +TEST_F(CppBoundClassTest, SetAndGetPropertiesWithCallbacks) { + // TODO(dglazkov): fix NPObject issues around failing property setters and + // getters and add tests for situations when GetProperty or SetProperty fail. + std::string js = "var result = 'SUCCESS';\n" + "example.my_value_with_callback = 10;\n" + "if (example.my_value_with_callback != 10)\n" + " result = 'FAIL: unable to set property.';\n" + "example.my_value_with_callback = 11;\n" + "if (example.my_value_with_callback != 11)\n" + " result = 'FAIL: unable to set property again';\n" + "if (example.same != 42)\n" + " result = 'FAIL: same property should always be 42';\n" + "example.same = 24;\n" + "if (example.same != 42)\n" + " result = 'FAIL: same property should always be 42';\n" + "document.writeln(result);\n"; + CheckJavaScriptSuccess(js); +} + +TEST_F(CppBoundClassTest, InvokeMethods) { + // The expression on the left is expected to return the value on the right. + static const std::string tests[] = { + "example.echoValue(true)", "true", + "example.echoValue(13)", "13", + "example.echoValue(2.718)", "2.718", + "example.echoValue('yes')", "'yes'", + "example.echoValue()", "null", // Too few arguments + + "example.echoType(false)", "true", + "example.echoType(19)", "7", + "example.echoType(9.876)", "3.14159", + "example.echoType('test string')", "'Success!'", + "example.echoType()", "null", // Too few arguments + + // Comparing floats that aren't integer-valued is usually problematic due + // to rounding, but exact powers of 2 should also be safe. + "example.plus(2.5, 18.0)", "20.5", + "example.plus(2, 3.25)", "5.25", + "example.plus(2, 3)", "5", + "example.plus()", "null", // Too few arguments + "example.plus(1)", "null", // Too few arguments + "example.plus(1, 'test')", "null", // Wrong argument type + "example.plus('test', 2)", "null", // Wrong argument type + "example.plus('one', 'two')", "null", // Wrong argument type + "" // Array end marker: insert additional test pairs before this. + }; + + for (int i = 0; tests[i] != ""; i+= 2) { + std::string left = tests[i]; + std::string right = tests[i + 1]; + std::string js = BuildJSCondition(left, right); + CheckJavaScriptSuccess(js); + } + + std::string js = "example.my_value = 3.25; example.my_other_value = 1.25;"; + js.append(BuildJSCondition( + "example.plus(example.my_value, example.my_other_value)", "4.5")); + CheckJavaScriptSuccess(js); +} + +// Tests that invoking a nonexistent method with no fallback method stops the +// script's execution +TEST_F(CppBoundClassTest, + InvokeNonexistentMethodNoFallback) { + std::string js = "example.nonExistentMethod();document.writeln('SUCCESS');"; + CheckJavaScriptFailure(js); +} + +// Ensures existent methods can be invoked successfully when the fallback method +// is used +TEST_F(CppBoundClassWithFallbackMethodTest, + InvokeExistentMethodsWithFallback) { + std::string js = BuildJSCondition("example.echoValue(34)", "34"); + CheckJavaScriptSuccess(js); +} + +} // namespace diff --git a/webkit/glue/cpp_variant.cc b/webkit/glue/cpp_variant.cc new file mode 100644 index 0000000..a254669 --- /dev/null +++ b/webkit/glue/cpp_variant.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2006-2008 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. + +// This file contains definitions for CppVariant. + +#include <limits> +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/cpp_variant.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +using WebKit::WebBindings; + +CppVariant::CppVariant() { + type = NPVariantType_Null; +} + +// Note that Set() performs a deep copy, which is necessary to safely +// call FreeData() on the value in the destructor. +CppVariant::CppVariant(const CppVariant& original) { + type = NPVariantType_Null; + Set(original); +} + +// See comment for copy constructor, above. +CppVariant& CppVariant::operator=(const CppVariant& original) { + if (&original != this) + Set(original); + return *this; +} + +CppVariant::~CppVariant() { + FreeData(); +} + +void CppVariant::FreeData() { + WebBindings::releaseVariantValue(this); +} + +bool CppVariant::isEqual(const CppVariant& other) const { + if (type != other.type) + return false; + + switch (type) { + case NPVariantType_Bool: { + return (value.boolValue == other.value.boolValue); + } + case NPVariantType_Int32: { + return (value.intValue == other.value.intValue); + } + case NPVariantType_Double: { + return (value.doubleValue == other.value.doubleValue); + } + case NPVariantType_String: { + const NPString *this_value = &value.stringValue; + const NPString *other_value = &other.value.stringValue; + uint32_t len = this_value->UTF8Length; + return (len == other_value->UTF8Length && + !strncmp(this_value->UTF8Characters, other_value->UTF8Characters, + len)); + } + case NPVariantType_Null: + case NPVariantType_Void: { + return true; + } + case NPVariantType_Object: { + NPObject *this_value = value.objectValue; + NPObject *other_value = other.value.objectValue; + return (this_value->_class == other_value->_class && + this_value->referenceCount == other_value->referenceCount); + } + } + return false; +} + +void CppVariant::CopyToNPVariant(NPVariant* result) const { + result->type = type; + switch (type) { + case NPVariantType_Bool: + result->value.boolValue = value.boolValue; + break; + case NPVariantType_Int32: + result->value.intValue = value.intValue; + break; + case NPVariantType_Double: + result->value.doubleValue = value.doubleValue; + break; + case NPVariantType_String: + WebBindings::initializeVariantWithStringCopy(result, &value.stringValue); + break; + case NPVariantType_Null: + case NPVariantType_Void: + // Nothing to set. + break; + case NPVariantType_Object: + result->type = NPVariantType_Object; + result->value.objectValue = WebBindings::retainObject(value.objectValue); + break; + } +} + +void CppVariant::Set(const NPVariant& new_value) { + FreeData(); + switch (new_value.type) { + case NPVariantType_Bool: + Set(new_value.value.boolValue); + break; + case NPVariantType_Int32: + Set(new_value.value.intValue); + break; + case NPVariantType_Double: + Set(new_value.value.doubleValue); + break; + case NPVariantType_String: + Set(new_value.value.stringValue); + break; + case NPVariantType_Null: + case NPVariantType_Void: + type = new_value.type; + break; + case NPVariantType_Object: + Set(new_value.value.objectValue); + break; + } +} + +void CppVariant::SetNull() { + FreeData(); + type = NPVariantType_Null; +} + +void CppVariant::Set(bool new_value) { + FreeData(); + type = NPVariantType_Bool; + value.boolValue = new_value; +} + +void CppVariant::Set(int32_t new_value) { + FreeData(); + type = NPVariantType_Int32; + value.intValue = new_value; +} + +void CppVariant::Set(double new_value) { + FreeData(); + type = NPVariantType_Double; + value.doubleValue = new_value; +} + +// The new_value must be a null-terminated string. +void CppVariant::Set(const char* new_value) { + FreeData(); + type = NPVariantType_String; + NPString new_string = {new_value, + static_cast<uint32_t>(strlen(new_value))}; + WebBindings::initializeVariantWithStringCopy(this, &new_string); +} + +void CppVariant::Set(const std::string& new_value) { + FreeData(); + type = NPVariantType_String; + NPString new_string = {new_value.data(), + static_cast<uint32_t>(new_value.size())}; + WebBindings::initializeVariantWithStringCopy(this, &new_string); +} +void CppVariant::Set(const NPString& new_value) { + FreeData(); + type = NPVariantType_String; + WebBindings::initializeVariantWithStringCopy(this, &new_value); +} + +void CppVariant::Set(NPObject* new_value) { + FreeData(); + type = NPVariantType_Object; + value.objectValue = WebBindings::retainObject(new_value); +} + +std::string CppVariant::ToString() const { + DCHECK(isString()); + return std::string(value.stringValue.UTF8Characters, + value.stringValue.UTF8Length); +} + +int32_t CppVariant::ToInt32() const { + if (isInt32()) { + return value.intValue; + } else if (isDouble()) { + return static_cast<int32_t>(value.doubleValue); + } else { + NOTREACHED(); + return 0; + } +} + +double CppVariant::ToDouble() const { + if (isInt32()) { + return static_cast<double>(value.intValue); + } else if (isDouble()) { + return value.doubleValue; + } else { + NOTREACHED(); + return 0.0; + } +} + +bool CppVariant::ToBoolean() const { + DCHECK(isBool()); + return value.boolValue; +} + +std::vector<std::wstring> CppVariant::ToStringVector() const { + + DCHECK(isObject()); + std::vector<std::wstring> wstring_vector; + NPObject* np_value = value.objectValue; + NPIdentifier length_id = WebBindings::getStringIdentifier("length"); + + if (WebBindings::hasProperty(NULL, np_value, length_id)) { + NPVariant length_value; + if (WebBindings::getProperty(NULL, np_value, length_id, &length_value)) { + int length = 0; + // The length is a double in some cases. + if (NPVARIANT_IS_DOUBLE(length_value)) + length = static_cast<int>(NPVARIANT_TO_DOUBLE(length_value)); + else if (NPVARIANT_IS_INT32(length_value)) + length = NPVARIANT_TO_INT32(length_value); + WebBindings::releaseVariantValue(&length_value); + + // For sanity, only allow 100 items. + length = std::min(100, length); + for (int i = 0; i < length; ++i) { + // Get each of the items. + std::string index = StringPrintf("%d", i); + NPIdentifier index_id = WebBindings::getStringIdentifier(index.c_str()); + if (WebBindings::hasProperty(NULL, np_value, index_id)) { + NPVariant index_value; + if (WebBindings::getProperty(NULL, np_value, index_id, &index_value)) { + if (NPVARIANT_IS_STRING(index_value)) { + std::string string( + NPVARIANT_TO_STRING(index_value).UTF8Characters, + NPVARIANT_TO_STRING(index_value).UTF8Length); + wstring_vector.push_back(UTF8ToWide(string)); + } + WebBindings::releaseVariantValue(&index_value); + } + } + } + } + } + return wstring_vector; +} + +bool CppVariant::Invoke(const std::string& method, const CppVariant* args, + uint32 arg_count, CppVariant& result) const { + DCHECK(isObject()); + NPIdentifier method_name = WebBindings::getStringIdentifier(method.c_str()); + NPObject* np_object = value.objectValue; + if (WebBindings::hasMethod(NULL, np_object, method_name)) { + NPVariant r; + bool status = WebBindings::invoke(NULL, np_object, method_name, args, arg_count, &r); + result.Set(r); + return status; + } else { + return false; + } +} diff --git a/webkit/glue/cpp_variant.h b/webkit/glue/cpp_variant.h new file mode 100644 index 0000000..71b3166 --- /dev/null +++ b/webkit/glue/cpp_variant.h @@ -0,0 +1,110 @@ +// Copyright (c) 2006-2008 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. + +/* + This file contains the declaration for CppVariant, a type used by C++ classes + that are to be bound to JavaScript objects. + + CppVariant exists primarily as an interface between C++ callers and the + corresponding NPVariant type. CppVariant also provides a number of + convenience constructors and accessors, so that the NPVariantType values + don't need to be exposed, and a destructor to free any memory allocated for + string values. + + For a usage example, see cpp_binding_example.{h|cc}. +*/ + +#ifndef WEBKIT_GLUE_CPP_VARIANT_H__ +#define WEBKIT_GLUE_CPP_VARIANT_H__ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npruntime.h" + +class CppVariant : public NPVariant { + public: + CppVariant(); + ~CppVariant(); + void SetNull(); + void Set(bool value); + void Set(int32_t value); + void Set(double value); + + // Note that setting a CppVariant to a string value involves copying the + // string data, which must be freed with a call to FreeData() when the + // CppVariant is set to a different value or is no longer needed. Normally + // this is handled by the other Set() methods and by the destructor. + void Set(const char* value); // Must be a null-terminated string. + void Set(const std::string& value); + void Set(const NPString& new_value); + void Set(const NPVariant& new_value); + + // Note that setting a CppVariant to an NPObject involves ref-counting + // the actual object. FreeData() should only be called if the CppVariant + // is no longer needed. The other Set() methods handle this internally. + // Also, the object's NPClass is expected to be a static object: neither + // the NP runtime nor CPPVariant will ever free it. + void Set(NPObject* new_value); + + // These three methods all perform deep copies of any string data. This + // allows local CppVariants to be released by the destructor without + // corrupting their sources. In performance-critical code, or when strings + // are very long, avoid creating new CppVariants. + // In case of NPObject as the data, the copying involves ref-counting + // as opposed to deep-copying. The ref-counting ensures that sources don't + // get corrupted when the copies get destroyed. + void CopyToNPVariant(NPVariant* result) const; + CppVariant& operator=(const CppVariant& original); + CppVariant(const CppVariant& original); + + // Calls NPN_ReleaseVariantValue, which frees any string data + // held by the object and sets its type to null. + // In case of NPObject, the NPN_ReleaseVariantValue decrements + // the ref-count (releases when ref-count becomes 0) + void FreeData(); + + // Compares this CppVariant's type and value to another's. They must be + // identical in both type and value to be considered equal. For string and + // object types, a deep comparison is performed; that is, the contents of the + // strings, or the classes and refcounts of the objects, must be the same, + // but they need not be the same pointers. + bool isEqual(const CppVariant& other) const; + + // The value of a CppVariant may be read directly from its NPVariant (but + // should only be set using one of the Set() methods above). Although the + // type of a CppVariant is likewise public, it can be accessed through these + // functions rather than directly if a caller wishes to avoid dependence on + // the NPVariantType values. + bool isBool() const { return (type == NPVariantType_Bool); } + bool isInt32() const { return (type == NPVariantType_Int32); } + bool isDouble() const { return (type == NPVariantType_Double); } + bool isNumber() const { return (isInt32() || isDouble()); } + bool isString() const { return (type == NPVariantType_String); } + bool isVoid() const { return (type == NPVariantType_Void); } + bool isNull() const { return (type == NPVariantType_Null); } + bool isEmpty() const { return (isVoid() || isNull()); } + bool isObject() const { return (type == NPVariantType_Object); } + + // Converters. The CppVariant must be of a type convertible to these values. + // For example, ToInteger() works only if isNumber() is true. + std::string ToString() const; + int32_t ToInt32() const; + double ToDouble() const; + bool ToBoolean() const; + // Returns a vector of strings for the specified argument. This is useful + // for converting a JavaScript array of strings into a vector of strings. + std::vector<std::wstring> ToStringVector() const; + + // Invoke method of the given name on an object with the supplied arguments. + // The first argument should be the object on which the method is to be + // invoked. Returns whether the method was successfully invoked. If the + // method was invoked successfully, any return value is stored in the + // CppVariant specified by result. + bool Invoke(const std::string& method, const CppVariant* args, + uint32 arg_count, CppVariant& result) const; +}; + +#endif // WEBKIT_GLUE_CPP_VARIANT_H__ diff --git a/webkit/glue/cpp_variant_unittest.cc b/webkit/glue/cpp_variant_unittest.cc new file mode 100644 index 0000000..43c78de --- /dev/null +++ b/webkit/glue/cpp_variant_unittest.cc @@ -0,0 +1,426 @@ +// Copyright (c) 2006-2008 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/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/cpp_variant.h" + +using WebKit::WebBindings; + +// Creates a std::string from an NPVariant of string type. If the NPVariant +// is not a string, empties the std::string. +void MakeStdString(const NPVariant& np, std::string* std_string) { + if (np.type == NPVariantType_String) { + const char* chars = + reinterpret_cast<const char*>(np.value.stringValue.UTF8Characters); + (*std_string).assign(chars, np.value.stringValue.UTF8Length); + } else { + (*std_string).clear(); + } +} + +// Verifies that the actual NPVariant is a string and that its value matches +// the expected_str. +void CheckString(const std::string& expected_str, const NPVariant& actual) { + EXPECT_EQ(NPVariantType_String, actual.type); + std::string actual_str; + MakeStdString(actual, &actual_str); + EXPECT_EQ(expected_str, actual_str); +} + +// Verifies that both the actual and the expected NPVariants are strings and +// that their values match. +void CheckString(const NPVariant& expected, const NPVariant& actual) { + EXPECT_EQ(NPVariantType_String, expected.type); + std::string expected_str; + MakeStdString(expected, &expected_str); + CheckString(expected_str, actual); +} + +int g_allocate_call_count = 0; +int g_deallocate_call_count = 0; + +void CheckObject(const NPVariant& actual) { + EXPECT_EQ(NPVariantType_Object, actual.type); + EXPECT_TRUE(actual.value.objectValue); + EXPECT_EQ(1U, actual.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + EXPECT_EQ(0, g_deallocate_call_count); +} + +NPObject* MockNPAllocate(NPP npp, NPClass* aClass) { + // This is a mock allocate method that mimics the behavior + // of WebBindings::createObject when allocate() is NULL + + ++g_allocate_call_count; + // Ignore npp and NPClass + return reinterpret_cast<NPObject*>(malloc(sizeof(NPObject))); +} + +void MockNPDeallocate(NPObject* npobj) { + // This is a mock deallocate method that mimics the behavior + // of NPN_DeallocateObject when deallocate() is NULL + + ++g_deallocate_call_count; + free(npobj); +} + +static NPClass void_class = { NP_CLASS_STRUCT_VERSION, + MockNPAllocate, + MockNPDeallocate, + 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + +NPObject* MakeVoidObject() { + g_allocate_call_count = 0; + g_deallocate_call_count = 0; + return WebBindings::createObject(NULL, &void_class); +} + +TEST(CppVariantTest, NewVariantHasNullType) { + CppVariant value; + EXPECT_EQ(NPVariantType_Null, value.type); +} + +TEST(CppVariantTest, SetNullSetsType) { + CppVariant value; + value.Set(17); + value.SetNull(); + EXPECT_EQ(NPVariantType_Null, value.type); +} + +TEST(CppVariantTest, CopyConstructorDoesDeepCopy) { + CppVariant source; + source.Set("test string"); + CppVariant dest = source; + EXPECT_EQ(NPVariantType_String, dest.type); + EXPECT_EQ(NPVariantType_String, source.type); + + // Ensure that the string was copied, not just the pointer. + EXPECT_NE(source.value.stringValue.UTF8Characters, + dest.value.stringValue.UTF8Characters); + + CheckString(source, dest); +} + +TEST(CppVariantTest, CopyConstructorIncrementsRefCount) { + CppVariant source; + NPObject *object = MakeVoidObject(); + source.Set(object); + // 2 references so far. + EXPECT_EQ(2U, source.value.objectValue->referenceCount); + + CppVariant dest = source; + EXPECT_EQ(3U, dest.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + WebBindings::releaseObject(object); + source.SetNull(); + CheckObject(dest); +} + +TEST(CppVariantTest, AssignmentDoesDeepCopy) { + CppVariant source; + source.Set("test string"); + CppVariant dest; + dest = source; + EXPECT_EQ(NPVariantType_String, dest.type); + EXPECT_EQ(NPVariantType_String, source.type); + + // Ensure that the string was copied, not just the pointer. + EXPECT_NE(source.value.stringValue.UTF8Characters, + dest.value.stringValue.UTF8Characters); + + CheckString(source, dest); +} + +TEST(CppVariantTest, AssignmentIncrementsRefCount) { + CppVariant source; + NPObject *object = MakeVoidObject(); + source.Set(object); + // 2 references so far. + EXPECT_EQ(2U, source.value.objectValue->referenceCount); + + CppVariant dest; + dest = source; + EXPECT_EQ(3U, dest.value.objectValue->referenceCount); + EXPECT_EQ(1, g_allocate_call_count); + + WebBindings::releaseObject(object); + source.SetNull(); + CheckObject(dest); +} + +TEST(CppVariantTest, DestroyingCopyDoesNotCorruptSource) { + CppVariant source; + source.Set("test string"); + std::string before; + MakeStdString(source, &before); + { + CppVariant dest = source; + } + CheckString(before, source); + + NPObject *object = MakeVoidObject(); + source.Set(object); + { + CppVariant dest2 = source; + } + WebBindings::releaseObject(object); + CheckObject(source); +} + +TEST(CppVariantTest, CopiesTypeAndValueToNPVariant) { + NPVariant np; + CppVariant cpp; + + cpp.Set(true); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.boolValue, np.value.boolValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set(17); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.intValue, np.value.intValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set(3.1415); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + EXPECT_EQ(cpp.value.doubleValue, np.value.doubleValue); + WebBindings::releaseVariantValue(&np); + + cpp.Set("test value"); + cpp.CopyToNPVariant(&np); + CheckString("test value", np); + WebBindings::releaseVariantValue(&np); + + cpp.SetNull(); + cpp.CopyToNPVariant(&np); + EXPECT_EQ(cpp.type, np.type); + WebBindings::releaseVariantValue(&np); + + NPObject *object = MakeVoidObject(); + cpp.Set(object); + cpp.CopyToNPVariant(&np); + WebBindings::releaseObject(object); + cpp.SetNull(); + CheckObject(np); + WebBindings::releaseVariantValue(&np); +} + +TEST(CppVariantTest, SetsTypeAndValueFromNPVariant) { + NPVariant np; + CppVariant cpp; + + VOID_TO_NPVARIANT(np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + WebBindings::releaseVariantValue(&np); + + NULL_TO_NPVARIANT(np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + WebBindings::releaseVariantValue(&np); + + BOOLEAN_TO_NPVARIANT(true, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.boolValue, cpp.value.boolValue); + WebBindings::releaseVariantValue(&np); + + INT32_TO_NPVARIANT(15, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.intValue, cpp.value.intValue); + WebBindings::releaseVariantValue(&np); + + DOUBLE_TO_NPVARIANT(2.71828, np); + cpp.Set(np); + EXPECT_EQ(np.type, cpp.type); + EXPECT_EQ(np.value.doubleValue, cpp.value.doubleValue); + WebBindings::releaseVariantValue(&np); + + NPString np_ascii_str = { "1st test value", + static_cast<uint32_t>(strlen("1st test value")) }; + WebBindings::initializeVariantWithStringCopy(&np, &np_ascii_str); + cpp.Set(np); + CheckString("1st test value", cpp); + WebBindings::releaseVariantValue(&np); + + // Test characters represented in 2/3/4 bytes in UTF-8 + // Greek alpha, Chinese number 1 (horizontal bar), + // Deseret letter (similar to 'O') + NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", + static_cast<uint32_t>(strlen( + "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) }; + WebBindings::initializeVariantWithStringCopy(&np, &np_intl_str); + cpp.Set(np); + CheckString("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", cpp); + WebBindings::releaseVariantValue(&np); + + NPObject *obj = MakeVoidObject(); + OBJECT_TO_NPVARIANT(obj, np); // Doesn't make a copy. + cpp.Set(np); + // Use this or WebBindings::releaseObject but NOT both. + WebBindings::releaseVariantValue(&np); + CheckObject(cpp); +} + +TEST(CppVariantTest, SetsSimpleTypesAndValues) { + CppVariant cpp; + cpp.Set(true); + EXPECT_EQ(NPVariantType_Bool, cpp.type); + EXPECT_EQ(true, cpp.value.boolValue); + + cpp.Set(5); + EXPECT_EQ(NPVariantType_Int32, cpp.type); + EXPECT_EQ(5, cpp.value.intValue); + + cpp.Set(1.234); + EXPECT_EQ(NPVariantType_Double, cpp.type); + EXPECT_EQ(1.234, cpp.value.doubleValue); + + // C string + cpp.Set("1st test string"); + CheckString("1st test string", cpp); + + // std::string + std::string source("std test string"); + cpp.Set(source); + CheckString("std test string", cpp); + + // NPString + NPString np_ascii_str = { "test NPString", + static_cast<uint32_t>(strlen("test NPString")) }; + cpp.Set(np_ascii_str); + std::string expected("test NPString"); + CheckString(expected, cpp); + + // Test characters represented in 2/3/4 bytes in UTF-8 + // Greek alpha, Chinese number 1 (horizontal bar), + // Deseret letter (similar to 'O') + NPString np_intl_str = { "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84", + static_cast<uint32_t>(strlen( + "\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84")) }; + cpp.Set(np_intl_str); + expected = std::string("\xce\xb1\xe4\xb8\x80\xf0\x90\x90\x84"); + CheckString(expected, cpp); + + NPObject* obj = MakeVoidObject(); + cpp.Set(obj); + WebBindings::releaseObject(obj); + CheckObject(cpp); +} + +TEST(CppVariantTest, FreeDataSetsToVoid) { + CppVariant cpp; + EXPECT_EQ(NPVariantType_Null, cpp.type); + cpp.Set(12); + EXPECT_EQ(NPVariantType_Int32, cpp.type); + cpp.FreeData(); + EXPECT_EQ(NPVariantType_Void, cpp.type); +} + +TEST(CppVariantTest, FreeDataReleasesObject) { + CppVariant cpp; + NPObject* object = MakeVoidObject(); + cpp.Set(object); + EXPECT_EQ(2U, object->referenceCount); + cpp.FreeData(); + EXPECT_EQ(1U, object->referenceCount); + EXPECT_EQ(0, g_deallocate_call_count); + + cpp.Set(object); + WebBindings::releaseObject(object); + EXPECT_EQ(0, g_deallocate_call_count); + cpp.FreeData(); + EXPECT_EQ(1, g_deallocate_call_count); +} + +TEST(CppVariantTest, IsTypeFunctionsWork) { + CppVariant cpp; + // These should not happen in practice, since voids are not supported + // This test must be first since it just clobbers internal data without + // releasing. + VOID_TO_NPVARIANT(cpp); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_TRUE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_TRUE(cpp.isEmpty()); + + cpp.Set(true); + EXPECT_TRUE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set(12); + EXPECT_FALSE(cpp.isBool()); + EXPECT_TRUE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_TRUE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set(3.1415); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_TRUE(cpp.isDouble()); + EXPECT_TRUE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.Set("a string"); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_TRUE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + cpp.SetNull(); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_TRUE(cpp.isNull()); + EXPECT_TRUE(cpp.isEmpty()); + EXPECT_FALSE(cpp.isObject()); + + NPObject *obj = MakeVoidObject(); + cpp.Set(obj); + EXPECT_FALSE(cpp.isBool()); + EXPECT_FALSE(cpp.isInt32()); + EXPECT_FALSE(cpp.isDouble()); + EXPECT_FALSE(cpp.isNumber()); + EXPECT_FALSE(cpp.isString()); + EXPECT_FALSE(cpp.isVoid()); + EXPECT_FALSE(cpp.isNull()); + EXPECT_FALSE(cpp.isEmpty()); + EXPECT_TRUE(cpp.isObject()); + WebBindings::releaseObject(obj); + CheckObject(cpp); +} diff --git a/webkit/glue/devtools_message_data.cc b/webkit/glue/devtools_message_data.cc new file mode 100644 index 0000000..a9af3c5 --- /dev/null +++ b/webkit/glue/devtools_message_data.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2010 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 "webkit/glue/devtools_message_data.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDevToolsMessageData.h" + +using WebKit::WebDevToolsMessageData; +using WebKit::WebString; +using WebKit::WebVector; + +DevToolsMessageData::DevToolsMessageData(const WebDevToolsMessageData& data) + : class_name(data.className.utf8()), + method_name(data.methodName.utf8()) { + for (size_t i = 0; i < data.arguments.size(); i++) + arguments.push_back(data.arguments[i].utf8()); +} + +WebDevToolsMessageData DevToolsMessageData::ToWebDevToolsMessageData() const { + WebDevToolsMessageData result; + result.className = WebString::fromUTF8(class_name); + result.methodName = WebString::fromUTF8(method_name); + WebVector<WebString> web_args(arguments.size()); + for (size_t i = 0; i < arguments.size(); i++) + web_args[i] = WebString::fromUTF8(arguments[i]); + result.arguments.swap(web_args); + return result; +} diff --git a/webkit/glue/devtools_message_data.h b/webkit/glue/devtools_message_data.h new file mode 100644 index 0000000..31daa14 --- /dev/null +++ b/webkit/glue/devtools_message_data.h @@ -0,0 +1,25 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_DEVTOOLS_MESSAGE_DATA_H_ +#define WEBKIT_GLUE_DEVTOOLS_MESSAGE_DATA_H_ + +#include <string> +#include <vector> + +namespace WebKit { +struct WebDevToolsMessageData; +} + +struct DevToolsMessageData { + DevToolsMessageData() {} + explicit DevToolsMessageData(const WebKit::WebDevToolsMessageData&); + WebKit::WebDevToolsMessageData ToWebDevToolsMessageData() const; + + std::string class_name; + std::string method_name; + std::vector<std::string> arguments; +}; + +#endif // WEBKIT_GLUE_DEVTOOLS_DEVTOOLS_MESSAGE_DATA_H_ diff --git a/webkit/glue/devtools_strings.grd b/webkit/glue/devtools_strings.grd new file mode 100644 index 0000000..a21baf5 --- /dev/null +++ b/webkit/glue/devtools_strings.grd @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- This file contains definitions of strings that are specific to +Google Chrome Developer Tools. --> + +<grit base_dir="." latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <output filename="devtoolsStrings_am.js" type="js_map_format" lang="am" /> + <output filename="devtoolsStrings_ar.js" type="js_map_format" lang="ar" /> + <output filename="devtoolsStrings_bg.js" type="js_map_format" lang="bg" /> + <output filename="devtoolsStrings_bn.js" type="js_map_format" lang="bn" /> + <output filename="devtoolsStrings_ca.js" type="js_map_format" lang="ca" /> + <output filename="devtoolsStrings_cs.js" type="js_map_format" lang="cs" /> + <output filename="devtoolsStrings_da.js" type="js_map_format" lang="da" /> + <output filename="devtoolsStrings_de.js" type="js_map_format" lang="de" /> + <output filename="devtoolsStrings_el.js" type="js_map_format" lang="el" /> + <output filename="devtoolsStrings_en-GB.js" type="js_map_format" lang="en-GB" /> + <output filename="devtoolsStrings_en-US.js" type="js_map_format" lang="en" /> + <output filename="devtoolsStrings_es.js" type="js_map_format" lang="es" /> + <output filename="devtoolsStrings_es-419.js" type="js_map_format" lang="es-419" /> + <output filename="devtoolsStrings_et.js" type="js_map_format" lang="et" /> + <output filename="devtoolsStrings_fi.js" type="js_map_format" lang="fi" /> + <output filename="devtoolsStrings_fil.js" type="js_map_format" lang="fil" /> + <output filename="devtoolsStrings_fr.js" type="js_map_format" lang="fr" /> + <output filename="devtoolsStrings_gu.js" type="js_map_format" lang="gu" /> + <output filename="devtoolsStrings_he.js" type="js_map_format" lang="he" /> + <output filename="devtoolsStrings_hi.js" type="js_map_format" lang="hi" /> + <output filename="devtoolsStrings_hr.js" type="js_map_format" lang="hr" /> + <output filename="devtoolsStrings_hu.js" type="js_map_format" lang="hu" /> + <output filename="devtoolsStrings_id.js" type="js_map_format" lang="id" /> + <output filename="devtoolsStrings_it.js" type="js_map_format" lang="it" /> + <output filename="devtoolsStrings_ja.js" type="js_map_format" lang="ja" /> + <output filename="devtoolsStrings_kn.js" type="js_map_format" lang="kn" /> + <output filename="devtoolsStrings_ko.js" type="js_map_format" lang="ko" /> + <output filename="devtoolsStrings_lt.js" type="js_map_format" lang="lt" /> + <output filename="devtoolsStrings_lv.js" type="js_map_format" lang="lv" /> + <output filename="devtoolsStrings_ml.js" type="js_map_format" lang="ml" /> + <output filename="devtoolsStrings_mr.js" type="js_map_format" lang="mr" /> + <output filename="devtoolsStrings_nl.js" type="js_map_format" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="devtoolsStrings_nb.js" type="js_map_format" lang="no" /> + <output filename="devtoolsStrings_or.js" type="js_map_format" lang="or" /> + <output filename="devtoolsStrings_pl.js" type="js_map_format" lang="pl" /> + <output filename="devtoolsStrings_pt-BR.js" type="js_map_format" lang="pt-BR" /> + <output filename="devtoolsStrings_pt-PT.js" type="js_map_format" lang="pt-PT" /> + <output filename="devtoolsStrings_ro.js" type="js_map_format" lang="ro" /> + <output filename="devtoolsStrings_ru.js" type="js_map_format" lang="ru" /> + <output filename="devtoolsStrings_sk.js" type="js_map_format" lang="sk" /> + <output filename="devtoolsStrings_sl.js" type="js_map_format" lang="sl" /> + <output filename="devtoolsStrings_sr.js" type="js_map_format" lang="sr" /> + <output filename="devtoolsStrings_sv.js" type="js_map_format" lang="sv" /> + <output filename="devtoolsStrings_sw.js" type="js_map_format" lang="sw" /> + <output filename="devtoolsStrings_ta.js" type="js_map_format" lang="ta" /> + <output filename="devtoolsStrings_te.js" type="js_map_format" lang="te" /> + <output filename="devtoolsStrings_th.js" type="js_map_format" lang="th" /> + <output filename="devtoolsStrings_tr.js" type="js_map_format" lang="tr" /> + <output filename="devtoolsStrings_uk.js" type="js_map_format" lang="uk" /> + <output filename="devtoolsStrings_vi.js" type="js_map_format" lang="vi" /> + <output filename="devtoolsStrings_zh-CN.js" type="js_map_format" lang="zh-CN" /> + <output filename="devtoolsStrings_zh-TW.js" type="js_map_format" lang="zh-TW" /> + </outputs> + <translations> + <!-- TODO add references to each of the XTB files (from the Translation + Console) that contain translations of messages in your project. Each + takes a form like <file path="english.xtb" />. Remember that all file + references are relative to this .grd file. --> + </translations> + <release seq="1"> + <messages fallback_to_english="true"> + <message name="IDS_FAILED_TO_RESOLVE_CHILDREN" desc="Message indicating that resolving element children has failed."> + Failed to resolve children: <ph name="ERROR">%s<ex>Allocation failure</ex></ph> + </message> + <message name="IDS_CORRUPT_OBJECT" desc="Error message showing corrupt object."> + Corrupted object: <ph name="OBJECT">%s<ex>bad</ex></ph> + </message> + <message name="IDS_SCOPE_VARS_FAILURE" desc="Error message indicating failure to resolve scope variables."> + Failed to resolve scope variables: <ph name="ERROR">%s<ex>Undefined variable</ex></ph> + </message> + <message name="IDS_PROCESSING_PROFILE" desc="Message indicating that profile is being processed."> + Processing... + </message> + <message name="IDS_PROFILE_TICKS_PROCESSED" desc="Message showing how many ticks are already processed."> + <ph name="COUNT">%d<ex>1000</ex></ph> ticks processed + </message> + <message name="IDS_NO_SOURCE" desc="Label for a script with no source."> + <source is not available> + </message> + <message name="IDS_UNKNOWN_SCOPE_TYPE" desc="Label for an unknown scope type."> + <Unknown scope type> + </message> + <message name="IDS_WINDOW_HEADER" desc="DevTools window header."> + Developer Tools - <ph name="URL">%s<ex>http://www.example.com/</ex></ph> + </message> + <message name="IDS_HEAP_TAB_TITLE" desc="Title of the heap profiler tab."> + Heap + </message> + <message name="IDS_TAKE_HEAP_SNAPSHOT" desc="Title of a button that takes heap snapshot."> + Take heap snapshot. + </message> + <message name="IDS_HEAP_SNAPSHOT" desc="Heap snapshot title."> + Snapshot <ph name="COUNT">%d<ex>1</ex></ph> + </message> + <message name="IDS_HEAP_USAGE" desc="Heap usage."> + Used <ph name="USED">%1$s<ex>100MB</ex></ph> of <ph name="CAPACITY">%2$s<ex>200MB</ex></ph> (<ph name="PERCENT">%3$.0f<ex>50</ex></ph>%%) + </message> + <message name="IDS_HEAP_SNAPSHOT_GRID_CONSTRUCTOR" desc="Title of a heap snapshot grid column showing object's constructor name."> + Constructor + </message> + <message name="IDS_HEAP_SNAPSHOT_GRID_COUNT" desc="Title of a heap snapshot grid column showing objects count."> + Count + </message> + <message name="IDS_HEAP_SNAPSHOT_GRID_COUNT_DELTA" desc="Title of a heap snapshot grid column showing delta of objects count."> + ± Count + </message> + <message name="IDS_HEAP_SNAPSHOT_GRID_SIZE_DELTA" desc="Title of a heap snapshot grid column showing delta of objects size."> + ± Size + </message> + <message name="IDS_HEAP_SNAPSHOT_COMPARED_TO" desc="An option to choose a snapshot to compare current snapshot with."> + Compared to <ph name="BASE">%s<ex>Snapshot 1</ex></ph> + </message> + <message name="IDS_ABSOLUTE_OBJ_COUNTS_AND_SIZES" desc="An option to show absolute objects counts and sizes."> + Show absolute counts and sizes. + </message> + <message name="IDS_PERCENTAGE_OBJ_COUNTS_AND_SIZES" desc="An option to show objects counts and sizes as percentages."> + Show counts and sizes as percentages. + </message> + <message name="IDS_NEW_OBJECTS_IN_DELTA" desc="Indicates that object instances are new compared to base state."> + new + </message> + <message name="IDS_DELETED_OBJECTS_IN_DELTA" desc="Indicates that object instances have disappeared compared to base state."> + deleted + </message> + <message name="IDS_VERY_LARGE_DELTA" desc="Shows very large delta in percents"> + <ph name="SIGN">%s<ex>+</ex></ph> >1000%% + </message> + <message name="IDS_SHARE_IN_PERCENTS_SIGNED" desc="Share in percents, with sign."> + <ph name="SIGN">%$1s<ex>+</ex></ph><ph name="SHARE">%$2.2f<ex>5</ex></ph>%% + </message> + <message name="IDS_SIGNED_DELTA" desc="Signed delta."> + %$1s%$2d + </message> + <message name="IDS_SIGNED_STRING_DELTA" desc="Signed string delta."> + %$1s%$2s + </message> + <message name="IDS_OBJECTS_AND_DATA_SIZE_LABEL" desc="Label for objects and data size legend item."> + Objects and Data + </message> + <message name="IDS_CODE_SIZE_LABEL" desc="Label for code size legend item."> + Code + </message> + </messages> + </release> +</grit> diff --git a/webkit/glue/dom_operations.cc b/webkit/glue/dom_operations.cc new file mode 100644 index 0000000..82e5e3a --- /dev/null +++ b/webkit/glue/dom_operations.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2010 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 <set> + +#include "base/compiler_specific.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAnimationController.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/form_data.h" +#include "webkit/glue/password_form_dom_manager.h" +#include "webkit/glue/webpasswordautocompletelistener_impl.h" + +using WebKit::WebAnimationController; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebNodeList; +using WebKit::WebVector; +using WebKit::WebView; + +namespace { + +// Structure for storage the unique set of all savable resource links for +// making sure that no duplicated resource link in final result. The consumer +// of the SavableResourcesUniqueCheck is responsible for keeping these pointers +// valid for the lifetime of the SavableResourcesUniqueCheck instance. +struct SavableResourcesUniqueCheck { + // Unique set of all sub resource links. + std::set<GURL>* resources_set; + // Unique set of all frame links. + std::set<GURL>* frames_set; + // Collection of all frames we go through when getting all savable resource + // links. + std::vector<WebFrame*>* frames; + + SavableResourcesUniqueCheck() + : resources_set(NULL), + frames_set(NULL), + frames(NULL) {} + + SavableResourcesUniqueCheck(std::set<GURL>* resources_set, + std::set<GURL>* frames_set, std::vector<WebFrame*>* frames) + : resources_set(resources_set), + frames_set(frames_set), + frames(frames) {} +}; + +// Get all savable resource links from current element. One element might +// have more than one resource link. It is possible to have some links +// in one CSS stylesheet. +void GetSavableResourceLinkForElement( + const WebElement& element, + const WebDocument& current_doc, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result) { + + // Handle frame and iframe tag. + if (element.hasTagName("iframe") || + element.hasTagName("frame")) { + WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element); + if (sub_frame) + unique_check->frames->push_back(sub_frame); + return; + } + + // Check whether the node has sub resource URL or not. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull()) + return; + // Get absolute URL. + GURL u = current_doc.completeURL(value); + // ignore invalid URL + if (!u.is_valid()) + return; + // Ignore those URLs which are not standard protocols. Because FTP + // protocol does no have cache mechanism, we will skip all + // sub-resources if they use FTP protocol. + if (!u.SchemeIs("http") && !u.SchemeIs("https") && !u.SchemeIs("file")) + return; + // Ignore duplicated resource link. + if (!unique_check->resources_set->insert(u).second) + return; + result->resources_list->push_back(u); + // Insert referrer for above new resource link. + result->referrers_list->push_back(GURL()); +} + +// Get all savable resource links from current WebFrameImpl object pointer. +void GetAllSavableResourceLinksForFrame(WebFrame* current_frame, + SavableResourcesUniqueCheck* unique_check, + webkit_glue::SavableResourcesResult* result, + const char** savable_schemes) { + // Get current frame's URL. + GURL current_frame_url = current_frame->url(); + + // If url of current frame is invalid, ignore it. + if (!current_frame_url.is_valid()) + return; + + // If url of current frame is not a savable protocol, ignore it. + bool is_valid_protocol = false; + for (int i = 0; savable_schemes[i] != NULL; ++i) { + if (current_frame_url.SchemeIs(savable_schemes[i])) { + is_valid_protocol = true; + break; + } + } + if (!is_valid_protocol) + return; + + // If find same frame we have recorded, ignore it. + if (!unique_check->frames_set->insert(current_frame_url).second) + return; + + // Get current using document. + WebDocument current_doc = current_frame->document(); + // Go through all descent nodes. + WebNodeCollection all = current_doc.all(); + // Go through all node in this frame. + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + // We only save HTML resources. + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + GetSavableResourceLinkForElement(element, + current_doc, + unique_check, + result); + } +} + +} // namespace + +namespace webkit_glue { + +// Map element name to a list of pointers to corresponding elements to simplify +// form filling. +typedef std::map<string16, WebKit::WebInputElement> + FormInputElementMap; + +// Utility struct for form lookup and autofill. When we parse the DOM to lookup +// a form, in addition to action and origin URL's we have to compare all +// necessary form elements. To avoid having to look these up again when we want +// to fill the form, the FindFormElements function stores the pointers +// in a FormElements* result, referenced to ensure they are safe to use. +struct FormElements { + WebFormElement form_element; + FormInputElementMap input_elements; + FormElements() { + } +}; + +typedef std::vector<FormElements*> FormElementsList; + +// Internal implementation of FillForm API. +static bool FillFormImpl(FormElements* fe, const FormData& data) { + if (!fe->form_element.autoComplete()) + return false; + + std::map<string16, string16> data_map; + for (size_t i = 0; i < data.fields.size(); i++) + data_map[data.fields[i].name()] = data.fields[i].value(); + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement& element = it->second; + if (!element.value().isEmpty()) // Don't overwrite pre-filled values. + continue; + if (element.inputType() == WebInputElement::Password && + (!element.isEnabledFormControl() || element.hasAttribute("readonly"))) { + continue; // Don't fill uneditable password fields. + } + element.setValue(data_map[it->first]); + element.setAutofilled(true); + element.dispatchFormControlChangeEvent(); + } + + return false; +} + +// Helper to search the given form element for the specified input elements +// in |data|, and add results to |result|. +static bool FindFormInputElements(WebFormElement* fe, + const FormData& data, + FormElements* result) { + // Loop through the list of elements we need to find on the form in + // order to autofill it. If we don't find any one of them, abort + // processing this form; it can't be the right one. + for (size_t j = 0; j < data.fields.size(); j++) { + WebVector<WebNode> temp_elements; + fe->getNamedElements(data.fields[j].name(), temp_elements); + if (temp_elements.isEmpty()) { + // We didn't find a required element. This is not the right form. + // Make sure no input elements from a partially matched form + // in this iteration remain in the result set. + // Note: clear will remove a reference from each InputElement. + result->input_elements.clear(); + return false; + } + // This element matched, add it to our temporary result. It's possible + // there are multiple matches, but for purposes of identifying the form + // one suffices and if some function needs to deal with multiple + // matching elements it can get at them through the FormElement*. + // Note: This assignment adds a reference to the InputElement. + result->input_elements[data.fields[j].name()] = + temp_elements[0].to<WebInputElement>(); + } + return true; +} + +// Helper to locate form elements identified by |data|. +static void FindFormElements(WebView* view, + const FormData& data, + FormElementsList* results) { + DCHECK(view); + DCHECK(results); + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + + // Loop through each frame. + for (WebFrame* f = main_frame; f; f = f->traverseNext(false)) { + WebDocument doc = f->document(); + if (!doc.isHTMLDocument()) + continue; + + GURL full_origin(f->url()); + if (data.origin != full_origin.ReplaceComponents(rep)) + continue; + + WebVector<WebFormElement> forms; + f->forms(forms); + + for (size_t i = 0; i < forms.size(); ++i) { + WebFormElement fe = forms[i]; + // Action URL must match. + GURL full_action(f->document().completeURL(fe.action())); + if (data.action != full_action.ReplaceComponents(rep)) + continue; + + scoped_ptr<FormElements> curr_elements(new FormElements); + if (!FindFormInputElements(&fe, data, curr_elements.get())) + continue; + + // We found the right element. + // Note: this assignment adds a reference to |fe|. + curr_elements->form_element = fe; + results->push_back(curr_elements.release()); + } + } +} + +void FillPasswordForm(WebView* view, + const PasswordFormFillData& data) { + FormElementsList forms; + // We own the FormElements* in forms. + FindFormElements(view, data.basic_data, &forms); + FormElementsList::iterator iter; + for (iter = forms.begin(); iter != forms.end(); ++iter) { + scoped_ptr<FormElements> form_elements(*iter); + + // If wait_for_username is true, we don't want to initially fill the form + // until the user types in a valid username. + if (!data.wait_for_username) + FillFormImpl(form_elements.get(), data.basic_data); + + // Attach autocomplete listener to enable selecting alternate logins. + // First, get pointers to username element. + WebInputElement username_element = + form_elements->input_elements[data.basic_data.fields[0].name()]; + + // Get pointer to password element. (We currently only support single + // password forms). + WebInputElement password_element = + form_elements->input_elements[data.basic_data.fields[1].name()]; + + username_element.document().frame()->registerPasswordListener( + username_element, + new WebPasswordAutocompleteListenerImpl( + new WebInputElementDelegate(username_element), + new WebInputElementDelegate(password_element), + data)); + } +} + +WebString GetSubResourceLinkFromElement(const WebElement& element) { + const char* attribute_name = NULL; + if (element.hasTagName("img") || + element.hasTagName("script")) { + attribute_name = "src"; + } else if (element.hasTagName("input")) { + const WebInputElement input = element.toConst<WebInputElement>(); + if (input.inputType() == WebInputElement::Image) { + attribute_name = "src"; + } + } else if (element.hasTagName("body") || + element.hasTagName("table") || + element.hasTagName("tr") || + element.hasTagName("td")) { + attribute_name = "background"; + } else if (element.hasTagName("blockquote") || + element.hasTagName("q") || + element.hasTagName("del") || + element.hasTagName("ins")) { + attribute_name = "cite"; + } else if (element.hasTagName("link")) { + // If the link element is not linked to css, ignore it. + if (LowerCaseEqualsASCII(element.getAttribute("type"), "text/css")) { + // TODO(jnd): Add support for extracting links of sub-resources which + // are inside style-sheet such as @import, url(), etc. + // See bug: http://b/issue?id=1111667. + attribute_name = "href"; + } + } + if (!attribute_name) + return WebString(); + WebString value = element.getAttribute(WebString::fromUTF8(attribute_name)); + // If value has content and not start with "javascript:" then return it, + // otherwise return NULL. + if (!value.isNull() && !value.isEmpty() && + !StartsWithASCII(value.utf8(), "javascript:", false)) + return value; + + return WebString(); +} + +// Get all savable resource links from current webview, include main +// frame and sub-frame +bool GetAllSavableResourceLinksForCurrentPage(WebView* view, + const GURL& page_url, SavableResourcesResult* result, + const char** savable_schemes) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return false; + + std::set<GURL> resources_set; + std::set<GURL> frames_set; + std::vector<WebFrame*> frames; + SavableResourcesUniqueCheck unique_check(&resources_set, + &frames_set, + &frames); + + GURL main_page_gurl(main_frame->url()); + + // Make sure we are saving same page between embedder and webkit. + // If page has being navigated, embedder will get three empty vector, + // which will make the saving page job ended. + if (page_url != main_page_gurl) + return true; + + // First, process main frame. + frames.push_back(main_frame); + + // Check all resource in this page, include sub-frame. + for (int i = 0; i < static_cast<int>(frames.size()); ++i) { + // Get current frame's all savable resource links. + GetAllSavableResourceLinksForFrame(frames[i], &unique_check, result, + savable_schemes); + } + + // Since frame's src can also point to sub-resources link, so it is possible + // that some URLs in frames_list are also in resources_list. For those + // URLs, we will remove it from frame_list, only keep them in resources_list. + for (std::set<GURL>::iterator it = frames_set.begin(); + it != frames_set.end(); ++it) { + // Append unique frame source to savable frame list. + if (resources_set.find(*it) == resources_set.end()) + result->frames_list->push_back(*it); + } + + return true; +} + +// Sizes a single size (the width or height) from a 'sizes' attribute. A size +// matches must match the following regex: [1-9][0-9]*. +static int ParseSingleIconSize(const string16& text) { + // Size must not start with 0, and be between 0 and 9. + if (text.empty() || !(text[0] >= L'1' && text[0] <= L'9')) + return 0; + // Make sure all chars are from 0-9. + for (size_t i = 1; i < text.length(); ++i) { + if (!(text[i] >= L'0' && text[i] <= L'9')) + return 0; + } + int output; + if (!StringToInt(text, &output)) + return 0; + return output; +} + +// Parses an icon size. An icon size must match the following regex: +// [1-9][0-9]*x[1-9][0-9]*. +// If the input couldn't be parsed, a size with a width/height < 0 is returned. +static gfx::Size ParseIconSize(const string16& text) { + std::vector<string16> sizes; + SplitStringDontTrim(text, L'x', &sizes); + if (sizes.size() != 2) + return gfx::Size(); + + return gfx::Size(ParseSingleIconSize(sizes[0]), + ParseSingleIconSize(sizes[1])); +} + +bool ParseIconSizes(const string16& text, + std::vector<gfx::Size>* sizes, + bool* is_any) { + *is_any = false; + std::vector<string16> size_strings; + SplitStringAlongWhitespace(text, &size_strings); + for (size_t i = 0; i < size_strings.size(); ++i) { + if (EqualsASCII(size_strings[i], "any")) { + *is_any = true; + } else { + gfx::Size size = ParseIconSize(size_strings[i]); + if (size.width() <= 0 || size.height() <= 0) + return false; // Bogus size. + sizes->push_back(size); + } + } + if (*is_any && !sizes->empty()) { + // If is_any is true, it must occur by itself. + return false; + } + return (*is_any || !sizes->empty()); +} + +static void AddInstallIcon(const WebElement& link, + std::vector<WebApplicationInfo::IconInfo>* icons) { + WebString href = link.getAttribute("href"); + if (href.isNull() || href.isEmpty()) + return; + + // Get complete url. + GURL url = link.document().completeURL(href); + if (!url.is_valid()) + return; + + if (!link.hasAttribute("sizes")) + return; + + bool is_any = false; + std::vector<gfx::Size> icon_sizes; + if (!ParseIconSizes(link.getAttribute("sizes"), &icon_sizes, &is_any) || + is_any || + icon_sizes.size() != 1) { + return; + } + WebApplicationInfo::IconInfo icon_info; + icon_info.width = icon_sizes[0].width(); + icon_info.height = icon_sizes[0].height(); + icon_info.url = url; + icons->push_back(icon_info); +} + +void GetApplicationInfo(WebView* view, WebApplicationInfo* app_info) { + WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + WebDocument doc = main_frame->document(); + if (doc.isNull()) + return; + + WebElement head = main_frame->document().head(); + if (head.isNull()) + return; + + WebNodeList children = head.childNodes(); + for (unsigned i = 0; i < children.length(); ++i) { + WebNode child = children.item(i); + if (!child.isElementNode()) + continue; + WebElement elem = child.to<WebElement>(); + + if (elem.hasTagName("link")) { + std::string rel = elem.getAttribute("rel").utf8(); + // "rel" attribute may use either "icon" or "shortcut icon". + // see also + // <http://en.wikipedia.org/wiki/Favicon> + // <http://dev.w3.org/html5/spec/Overview.html#rel-icon> + if (LowerCaseEqualsASCII(rel, "icon") || + LowerCaseEqualsASCII(rel, "shortcut icon")) + AddInstallIcon(elem, &app_info->icons); + } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) { + std::string name = elem.getAttribute("name").utf8(); + WebString content = elem.getAttribute("content"); + if (name == "application-name") { + app_info->title = content; + } else if (name == "description") { + app_info->description = content; + } else if (name == "application-url") { + std::string url = content.utf8(); + GURL main_url = main_frame->url(); + app_info->app_url = main_url.is_valid() ? + main_url.Resolve(url) : GURL(url); + if (!app_info->app_url.is_valid()) + app_info->app_url = GURL(); + } + } + } +} + +bool PauseAnimationAtTimeOnElementWithId(WebView* view, + const std::string& animation_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseAnimationAtTime(element, + WebString::fromUTF8(animation_name), + time); +} + +bool PauseTransitionAtTimeOnElementWithId(WebView* view, + const std::string& property_name, + double time, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return false; + + WebElement element = + web_frame->document().getElementById(WebString::fromUTF8(element_id)); + if (element.isNull()) + return false; + return controller->pauseTransitionAtTime(element, + WebString::fromUTF8(property_name), + time); +} + +bool ElementDoesAutoCompleteForElementWithId(WebView* view, + const std::string& element_id) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return false; + + WebElement element = web_frame->document().getElementById( + WebString::fromUTF8(element_id)); + if (element.isNull() || !element.hasTagName("input")) + return false; + + WebInputElement input_element = element.to<WebInputElement>(); + return input_element.autoComplete(); +} + +int NumberOfActiveAnimations(WebView* view) { + WebFrame* web_frame = view->mainFrame(); + if (!web_frame) + return -1; + + WebAnimationController* controller = web_frame->animationController(); + if (!controller) + return -1; + + return controller->numberOfActiveAnimations(); +} + +void GetMetaElementsWithName(WebDocument* document, + const string16& name, + std::vector<WebElement>* meta_elements) { + DCHECK(document); + DCHECK(meta_elements); + meta_elements->clear(); + WebElement head = document->head(); + if (head.isNull() || !head.hasChildNodes()) + return; + + WebNodeList children = head.childNodes(); + for (size_t i = 0; i < children.length(); ++i) { + WebNode node = children.item(i); + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + if (!element.hasTagName("meta")) + continue; + WebString meta_name = element.getAttribute("name"); + if (meta_name.isNull() || meta_name != name) + continue; + meta_elements->push_back(element); + } +} + +} // webkit_glue diff --git a/webkit/glue/dom_operations.h b/webkit/glue/dom_operations.h new file mode 100644 index 0000000..664add0 --- /dev/null +++ b/webkit/glue/dom_operations.h @@ -0,0 +1,144 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_DOM_OPERATIONS_H__ +#define WEBKIT_GLUE_DOM_OPERATIONS_H__ + +#include <string> +#include <map> +#include <vector> + +#include "gfx/size.h" +#include "googleurl/src/gurl.h" + +namespace WebKit { +class WebDocument; +class WebElement; +class WebString; +class WebView; +} + +// A collection of operations that access the underlying WebKit DOM directly. +namespace webkit_glue { + +struct PasswordFormFillData; + +// Fill matching password forms and trigger autocomplete in the case of multiple +// matching logins. +void FillPasswordForm(WebKit::WebView* view, + const PasswordFormFillData& data); + +// Structure for storage the result of getting all savable resource links +// for current page. The consumer of the SavableResourcesResult is responsible +// for keeping these pointers valid for the lifetime of the +// SavableResourcesResult instance. +struct SavableResourcesResult { + // vector which contains all savable links of sub resource. + std::vector<GURL>* resources_list; + // vector which contains corresponding all referral links of sub resource, + // it matched with links one by one. + std::vector<GURL>* referrers_list; + // vector which contains all savable links of main frame and sub frames. + std::vector<GURL>* frames_list; + + // Constructor. + SavableResourcesResult(std::vector<GURL>* resources_list, + std::vector<GURL>* referrers_list, + std::vector<GURL>* frames_list) + : resources_list(resources_list), + referrers_list(referrers_list), + frames_list(frames_list) { } + + private: + DISALLOW_COPY_AND_ASSIGN(SavableResourcesResult); +}; + +// Get all savable resource links from current webview, include main frame +// and sub-frame. After collecting all savable resource links, this function +// will send those links to embedder. Return value indicates whether we get +// all saved resource links successfully. +bool GetAllSavableResourceLinksForCurrentPage(WebKit::WebView* view, + const GURL& page_url, SavableResourcesResult* savable_resources_result, + const char** savable_schemes); + +// Structure used when installing a web page as an app. Populated via +// GetApplicationInfo. +struct WebApplicationInfo { + struct IconInfo { + GURL url; + int width; + int height; + }; + + // Title of the application. This is set from the meta tag whose name is + // 'application-name'. + string16 title; + + // Description of the application. This is set from the meta tag whose name + // is 'description'. + string16 description; + + // URL for the app. This is set from the meta tag whose name is + // 'application-url'. + GURL app_url; + + // Set of available icons. This is set for all link tags whose rel=icon. Only + // icons that have a non-zero (width and/or height) are added. + std::vector<IconInfo> icons; +}; + +// Parses the icon's size attribute as defined in the HTML 5 spec. Returns true +// on success, false on errors. On success either all the sizes specified in +// the attribute are added to sizes, or is_any is set to true. +// +// You shouldn't have a need to invoke this directly, it's public for testing. +bool ParseIconSizes(const string16& text, + std::vector<gfx::Size>* sizes, + bool* is_any); + +// Gets the application info for the specified page. See the description of +// WebApplicationInfo for details as to where each field comes from. +void GetApplicationInfo(WebKit::WebView* view, WebApplicationInfo* app_info); + +// Invokes pauseAnimationAtTime on the AnimationController associated with the +// |view|s main frame. +// This is used by test shell. +bool PauseAnimationAtTimeOnElementWithId(WebKit::WebView* view, + const std::string& animation_name, + double time, + const std::string& element_id); + +// Invokes pauseTransitionAtTime on the AnimationController associated with the +// |view|s main frame. +// This is used by test shell. +bool PauseTransitionAtTimeOnElementWithId(WebKit::WebView* view, + const std::string& property_name, + double time, + const std::string& element_id); + +// Returns true if the element with |element_id| as its id has autocomplete +// on. +bool ElementDoesAutoCompleteForElementWithId(WebKit::WebView* view, + const std::string& element_id); + +// Returns the number of animations currently running. +int NumberOfActiveAnimations(WebKit::WebView* view); + +// Returns the value in an elements resource url attribute. For IMG, SCRIPT or +// INPUT TYPE=image, returns the value in "src". For LINK TYPE=text/css, returns +// the value in "href". For BODY, TABLE, TR, TD, returns the value in +// "background". For BLOCKQUOTE, Q, DEL, INS, returns the value in "cite" +// attribute. Otherwise returns a null WebString. +WebKit::WebString GetSubResourceLinkFromElement( + const WebKit::WebElement& element); + +// Puts the meta-elements of |document| that have the specified |name| in +// |meta_elements|. +void GetMetaElementsWithName(WebKit::WebDocument* document, + const string16& name, + std::vector<WebKit::WebElement>* meta_elements); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_DOM_OPERATIONS_H__ diff --git a/webkit/glue/dom_operations_unittest.cc b/webkit/glue/dom_operations_unittest.cc new file mode 100644 index 0000000..e9f590c --- /dev/null +++ b/webkit/glue/dom_operations_unittest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2006-2008 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/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +namespace { + +class DomOperationsTests : public TestShellTest { + public: + // Test function GetAllSavableResourceLinksForCurrentPage with a web page. + // We expect result of GetAllSavableResourceLinksForCurrentPage exactly + // matches expected_resources_set. + void GetSavableResourceLinksForPage(const FilePath& page_file_path, + const std::set<GURL>& expected_resources_set); + + protected: + // testing::Test + virtual void SetUp() { + TestShellTest::SetUp(); + } + + virtual void TearDown() { + TestShellTest::TearDown(); + } +}; + + +void DomOperationsTests::GetSavableResourceLinksForPage( + const FilePath& page_file_path, + const std::set<GURL>& expected_resources_set) { + // Convert local file path to file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + // Load the test file. + test_shell_->ResetTestController(); + test_shell_->LoadURL(file_url); + test_shell_->WaitTestFinished(); + // Get all savable resource links for the page. + std::vector<GURL> resources_list; + std::vector<GURL> referrers_list; + std::vector<GURL> frames_list; + webkit_glue::SavableResourcesResult result(&resources_list, + &referrers_list, + &frames_list); + + const char* savable_schemes[] = { + "http", + "https", + "file", + NULL + }; + + ASSERT_TRUE(webkit_glue::GetAllSavableResourceLinksForCurrentPage( + test_shell_->webView(), file_url, &result, savable_schemes)); + // Check all links of sub-resource + for (std::vector<GURL>::const_iterator cit = resources_list.begin(); + cit != resources_list.end(); ++cit) { + ASSERT_TRUE(expected_resources_set.find(*cit) != + expected_resources_set.end()); + } + // Check all links of frame. + for (std::vector<GURL>::const_iterator cit = frames_list.begin(); + cit != frames_list.end(); ++cit) { + ASSERT_TRUE(expected_resources_set.find(*cit) != + expected_resources_set.end()); + } +} + +// Test function GetAllSavableResourceLinksForCurrentPage with a web page +// which has valid savable resource links. +TEST_F(DomOperationsTests, GetSavableResourceLinksWithPageHasValidLinks) { + std::set<GURL> expected_resources_set; + // Set directory of test data. + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + + const char* expected_sub_resource_links[] = { + "file:///c:/yt/css/base_all-vfl36460.css", + "file:///c:/yt/js/base_all_with_bidi-vfl36451.js", + "file:///c:/yt/img/pixel-vfl73.gif" + }; + const char* expected_frame_links[] = { + "youtube_1.htm", + "youtube_2.htm" + }; + // Add all expected links of sub-resource to expected set. + for (size_t i = 0; i < arraysize(expected_sub_resource_links); ++i) + expected_resources_set.insert(GURL(expected_sub_resource_links[i])); + // Add all expected links of frame to expected set. + for (size_t i = 0; i < arraysize(expected_frame_links); ++i) { + const FilePath expected_frame_url = + page_file_path.AppendASCII(expected_frame_links[i]); + expected_resources_set.insert( + net::FilePathToFileURL(expected_frame_url)); + } + + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + GetSavableResourceLinksForPage(page_file_path, expected_resources_set); +} + +// Test function GetAllSavableResourceLinksForCurrentPage with a web page +// which does not have valid savable resource links. +TEST_F(DomOperationsTests, GetSavableResourceLinksWithPageHasInvalidLinks) { + std::set<GURL> expected_resources_set; + // Set directory of test data. + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + + const char* expected_frame_links[] = { + "youtube_2.htm" + }; + // Add all expected links of frame to expected set. + for (size_t i = 0; i < arraysize(expected_frame_links); ++i) { + FilePath expected_frame_url = + page_file_path.AppendASCII(expected_frame_links[i]); + expected_resources_set.insert( + net::FilePathToFileURL(expected_frame_url)); + } + + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + GetSavableResourceLinksForPage(page_file_path, expected_resources_set); +} + +// Tests ParseIconSizes with various input. +TEST_F(DomOperationsTests, ParseIconSizes) { + struct TestData { + const char* input; + const bool expected_result; + const bool is_any; + const size_t expected_size_count; + const int width1; + const int height1; + const int width2; + const int height2; + } data[] = { + // Bogus input cases. + { "10", false, false, 0, 0, 0, 0, 0 }, + { "10 10", false, false, 0, 0, 0, 0, 0 }, + { "010", false, false, 0, 0, 0, 0, 0 }, + { " 010 ", false, false, 0, 0, 0, 0, 0 }, + { " 10x ", false, false, 0, 0, 0, 0, 0 }, + { " x10 ", false, false, 0, 0, 0, 0, 0 }, + { "any 10x10", false, false, 0, 0, 0, 0, 0 }, + { "", false, false, 0, 0, 0, 0, 0 }, + { "10ax11", false, false, 0, 0, 0, 0, 0 }, + + // Any. + { "any", true, true, 0, 0, 0, 0, 0 }, + { " any", true, true, 0, 0, 0, 0, 0 }, + { " any ", true, true, 0, 0, 0, 0, 0 }, + + // Sizes. + { "10x11", true, false, 1, 10, 11, 0, 0 }, + { " 10x11 ", true, false, 1, 10, 11, 0, 0 }, + { " 10x11 1x2", true, false, 2, 10, 11, 1, 2 }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + bool is_any; + std::vector<gfx::Size> sizes; + bool result = webkit_glue::ParseIconSizes( + ASCIIToUTF16(data[i].input), &sizes, &is_any); + ASSERT_EQ(result, data[i].expected_result); + if (result) { + ASSERT_EQ(data[i].is_any, is_any); + ASSERT_EQ(data[i].expected_size_count, sizes.size()); + if (sizes.size() > 0) { + ASSERT_EQ(data[i].width1, sizes[0].width()); + ASSERT_EQ(data[i].height1, sizes[0].height()); + } + if (sizes.size() > 1) { + ASSERT_EQ(data[i].width2, sizes[1].width()); + ASSERT_EQ(data[i].height2, sizes[1].height()); + } + } + } +} + +} // namespace diff --git a/webkit/glue/dom_serializer_unittest.cc b/webkit/glue/dom_serializer_unittest.cc new file mode 100644 index 0000000..a1846f3 --- /dev/null +++ b/webkit/glue/dom_serializer_unittest.cc @@ -0,0 +1,850 @@ +// Copyright (c) 2006-2008 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/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/hash_tables.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_util.h" +#include "net/url_request/url_request_context.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPageSerializer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPageSerializerClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/dom_operations.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebCString; +using WebKit::WebData; +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebFrame; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebNodeList; +using WebKit::WebPageSerializer; +using WebKit::WebPageSerializerClient; +using WebKit::WebNode; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebView; +using WebKit::WebVector; + +namespace { + +// Iterate recursively over sub-frames to find one with with a given url. +WebFrame* FindSubFrameByURL(WebView* web_view, const GURL& url) { + if (!web_view->mainFrame()) + return NULL; + + std::vector<WebFrame*> stack; + stack.push_back(web_view->mainFrame()); + + while (!stack.empty()) { + WebFrame* current_frame = stack.back(); + stack.pop_back(); + if (GURL(current_frame->url()) == url) + return current_frame; + WebNodeCollection all = current_frame->document().all(); + for (WebNode node = all.firstItem(); + !node.isNull(); node = all.nextItem()) { + if (!node.isElementNode()) + continue; + // Check frame tag and iframe tag + WebElement element = node.to<WebElement>(); + if (!element.hasTagName("frame") && !element.hasTagName("iframe")) + continue; + WebFrame* sub_frame = WebFrame::fromFrameOwnerElement(element); + if (sub_frame) + stack.push_back(sub_frame); + } + } + return NULL; +} + +class DomSerializerTests : public TestShellTest, + public WebPageSerializerClient { + public: + DomSerializerTests() + : local_directory_name_(FILE_PATH_LITERAL("./dummy_files/")) { } + + // DomSerializerDelegate. + void didSerializeDataForFrame(const WebURL& frame_web_url, + const WebCString& data, + PageSerializationStatus status) { + + GURL frame_url(frame_web_url); + // If the all frames are finished saving, check all finish status + if (status == WebPageSerializerClient::AllFramesAreFinished) { + SerializationFinishStatusMap::iterator it = + serialization_finish_status_.begin(); + for (; it != serialization_finish_status_.end(); ++it) + ASSERT_TRUE(it->second); + serialized_ = true; + return; + } + + // Check finish status of current frame. + SerializationFinishStatusMap::iterator it = + serialization_finish_status_.find(frame_url.spec()); + // New frame, set initial status as false. + if (it == serialization_finish_status_.end()) + serialization_finish_status_[frame_url.spec()] = false; + + it = serialization_finish_status_.find(frame_url.spec()); + ASSERT_TRUE(it != serialization_finish_status_.end()); + // In process frame, finish status should be false. + ASSERT_FALSE(it->second); + + // Add data to corresponding frame's content. + serialized_frame_map_[frame_url.spec()] += data.data(); + + // Current frame is completed saving, change the finish status. + if (status == WebPageSerializerClient::CurrentFrameIsFinished) + it->second = true; + } + + bool HasSerializedFrame(const GURL& frame_url) { + return serialized_frame_map_.find(frame_url.spec()) != + serialized_frame_map_.end(); + } + + const std::string& GetSerializedContentForFrame( + const GURL& frame_url) { + return serialized_frame_map_[frame_url.spec()]; + } + + // Load web page according to specific URL. + void LoadPageFromURL(const GURL& page_url) { + // Load the test file. + test_shell_->ResetTestController(); + test_shell_->LoadURL(page_url); + test_shell_->WaitTestFinished(); + } + + // Load web page according to input content and relative URLs within + // the document. + void LoadContents(const std::string& contents, + const GURL& base_url, + const WebString encoding_info) { + test_shell_->ResetTestController(); + // If input encoding is empty, use UTF-8 as default encoding. + if (encoding_info.isEmpty()) { + test_shell_->webView()->mainFrame()->loadHTMLString(contents, base_url); + } else { + WebData data(contents.data(), contents.length()); + + // Do not use WebFrame.LoadHTMLString because it assumes that input + // html contents use UTF-8 encoding. + // TODO(darin): This should use WebFrame::loadData. + WebFrame* web_frame = + test_shell_->webView()->mainFrame(); + + ASSERT_TRUE(web_frame != NULL); + + web_frame->loadData(data, "text/html", encoding_info, base_url); + } + + test_shell_->WaitTestFinished(); + } + + // Serialize page DOM according to specific page URL. The parameter + // recursive_serialization indicates whether we will serialize all + // sub-frames. + void SerializeDomForURL(const GURL& page_url, + bool recursive_serialization) { + // Find corresponding WebFrame according to page_url. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), + page_url); + ASSERT_TRUE(web_frame != NULL); + // Add input file URl to links_. + links_.assign(&page_url,1); + // Add dummy file path to local_path_. + WebString file_path = webkit_glue::FilePathStringToWebString( + FILE_PATH_LITERAL("c:\\dummy.htm")); + local_paths_.assign(&file_path, 1); + // Start serializing DOM. + bool result = WebPageSerializer::serialize(web_frame, + recursive_serialization, + static_cast<WebPageSerializerClient*>(this), + links_, + local_paths_, + webkit_glue::FilePathToWebString(local_directory_name_)); + ASSERT_TRUE(result); + ASSERT_TRUE(serialized_); + } + + private: + // Map frame_url to corresponding serialized_content. + typedef base::hash_map<std::string, std::string> SerializedFrameContentMap; + SerializedFrameContentMap serialized_frame_map_; + // Map frame_url to corresponding status of serialization finish. + typedef base::hash_map<std::string, bool> SerializationFinishStatusMap; + SerializationFinishStatusMap serialization_finish_status_; + // Flag indicates whether the process of serializing DOM is finished or not. + bool serialized_; + // The links_ contain dummy original URLs of all saved links. + WebVector<WebURL> links_; + // The local_paths_ contain dummy corresponding local file paths of all saved + // links, which matched links_ one by one. + WebVector<WebString> local_paths_; + // The local_directory_name_ is dummy relative path of directory which + // contain all saved auxiliary files included all sub frames and resources. + const FilePath local_directory_name_; + + protected: + // testing::Test + virtual void SetUp() { + TestShellTest::SetUp(); + serialized_ = false; + } + + virtual void TearDown() { + TestShellTest::TearDown(); + } +}; + +// Helper function that test whether the first node in the doc is a doc type +// node. +bool HasDocType(const WebDocument& doc) { + WebNode node = doc.firstChild(); + if (node.isNull()) + return false; + return node.nodeType() == WebNode::DocumentTypeNode; +} + +// Helper function for checking whether input node is META tag. Return true +// means it is META element, otherwise return false. The parameter charset_info +// return actual charset info if the META tag has charset declaration. +bool IsMetaElement(const WebNode& node, std::string& charset_info) { + if (!node.isElementNode()) + return false; + const WebElement meta = node.toConst<WebElement>(); + if (!meta.hasTagName("meta")) + return false; + charset_info.erase(0, charset_info.length()); + // Check the META charset declaration. + WebString httpEquiv = meta.getAttribute("http-equiv"); + if (LowerCaseEqualsASCII(httpEquiv, "content-type")) { + std::string content = meta.getAttribute("content").utf8(); + int pos = content.find("charset", 0); + if (pos > -1) { + // Add a dummy charset declaration to charset_info, which indicates this + // META tag has charset declaration although we do not get correct value + // yet. + charset_info.append("has-charset-declaration"); + int remaining_length = content.length() - pos - 7; + if (!remaining_length) + return true; + int start_pos = pos + 7; + // Find "=" symbol. + while (remaining_length--) + if (content[start_pos++] == L'=') + break; + // Skip beginning space. + while (remaining_length) { + if (content[start_pos] > 0x0020) + break; + ++start_pos; + --remaining_length; + } + if (!remaining_length) + return true; + int end_pos = start_pos; + // Now we find out the start point of charset info. Search the end point. + while (remaining_length--) { + if (content[end_pos] <= 0x0020 || content[end_pos] == L';') + break; + ++end_pos; + } + // Get actual charset info. + charset_info = content.substr(start_pos, end_pos - start_pos); + return true; + } + } + return true; +} + +// If original contents have document type, the serialized contents also have +// document type. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithDocType) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Make sure original contents have document type. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(HasDocType(doc)); + // Do serialization. + SerializeDomForURL(file_url, false); + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure serialized contents still have document type. + web_frame = test_shell_->webView()->mainFrame(); + doc = web_frame->document(); + ASSERT_TRUE(HasDocType(doc)); +} + +// If original contents do not have document type, the serialized contents +// also do not have document type. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithoutDocType) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Make sure original contents do not have document type. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(!HasDocType(doc)); + // Do serialization. + SerializeDomForURL(file_url, false); + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure serialized contents do not have document type. + web_frame = test_shell_->webView()->mainFrame(); + doc = web_frame->document(); + ASSERT_TRUE(!HasDocType(doc)); +} + +// Serialize XML document which has all 5 built-in entities. After +// finishing serialization, the serialized contents should be same +// with original XML document. +TEST_F(DomSerializerTests, SerializeXMLDocWithBuiltInEntities) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("note.xml"); + // Read original contents for later comparison. + std::string original_contents; + ASSERT_TRUE(file_util::ReadFileToString(page_file_path, &original_contents)); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + ASSERT_EQ(original_contents, serialized_contents); +} + +// When serializing DOM, we add MOTW declaration before html tag. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithAddingMOTW) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + // Read original contents for later comparison . + std::string original_contents; + ASSERT_TRUE(file_util::ReadFileToString(page_file_path, &original_contents)); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Make sure original contents does not have MOTW; + std::string motw_declaration = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + ASSERT_FALSE(motw_declaration.empty()); + // The encoding of original contents is ISO-8859-1, so we convert the MOTW + // declaration to ASCII and search whether original contents has it or not. + ASSERT_TRUE(std::string::npos == + original_contents.find(motw_declaration)); + // Load the test file. + LoadPageFromURL(file_url); + // Do serialization. + SerializeDomForURL(file_url, false); + // Make sure the serialized contents have MOTW ; + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + ASSERT_FALSE(std::string::npos == + serialized_contents.find(motw_declaration)); +} + +// When serializing DOM, we will add the META which have correct charset +// declaration as first child of HEAD element for resolving WebKit bug: +// http://bugs.webkit.org/show_bug.cgi?id=16621 even the original document +// does not have META charset declaration. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithNoMetaCharsetInOriginalDoc) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_1.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + + // Make sure there is no META charset declaration in original document. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + // Go through all children of HEAD element. + for (WebNode child = head_element.firstChild(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure the first child of HEAD element is META which has charset + // declaration in serialized contents. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + WebNode meta_node = head_element.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info2; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info2)); + ASSERT_TRUE(!charset_info2.empty()); + ASSERT_TRUE(charset_info2 == std::string(web_frame->encoding().utf8())); + + // Make sure no more additional META tags which have charset declaration. + for (WebNode child = meta_node.nextSibling(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } +} + +// When serializing DOM, if the original document has multiple META charset +// declaration, we will add the META which have correct charset declaration +// as first child of HEAD element and remove all original META charset +// declarations. +TEST_F(DomSerializerTests, + SerializeHTMLDOMWithMultipleMetaCharsetInOriginalDoc) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("youtube_2.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + + // Make sure there are multiple META charset declarations in original + // document. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_ele = doc.head(); + ASSERT_TRUE(!head_ele.isNull()); + // Go through all children of HEAD element. + int charset_declaration_count = 0; + for (WebNode child = head_ele.firstChild(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info) && !charset_info.empty()) + charset_declaration_count++; + } + // The original doc has more than META tags which have charset declaration. + ASSERT_TRUE(charset_declaration_count > 1); + + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + // Make sure only first child of HEAD element is META which has charset + // declaration in serialized contents. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_ele = doc.head(); + ASSERT_TRUE(!head_ele.isNull()); + WebNode meta_node = head_ele.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info2; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info2)); + ASSERT_TRUE(!charset_info2.empty()); + ASSERT_TRUE(charset_info2 == std::string(web_frame->encoding().utf8())); + + // Make sure no more additional META tags which have charset declaration. + for (WebNode child = meta_node.nextSibling(); !child.isNull(); + child = child.nextSibling()) { + std::string charset_info; + if (IsMetaElement(child, charset_info)) + ASSERT_TRUE(charset_info.empty()); + } +} + +// Test situation of html entities in text when serializing HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEntitiesInText) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII( + "dom_serializer/htmlentities_in_text.htm"); + // Get file URL. The URL is dummy URL to identify the following loading + // actions. The test content is in constant:original_contents. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Test contents. + static const char* const original_contents = + "<html><body>&<>\"\'</body></html>"; + // Load the test contents. + LoadContents(original_contents, file_url, WebString()); + + // Get BODY's text content in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_ele = doc.body(); + ASSERT_TRUE(!body_ele.isNull()); + WebNode text_node = body_ele.firstChild(); + ASSERT_TRUE(text_node.isTextNode()); + ASSERT_TRUE(std::string(text_node.createMarkup().utf8()) == + "&<>\"\'"); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Compare the serialized contents with original contents to make sure + // they are same. + // Because we add MOTW when serializing DOM, so before comparison, we also + // need to add MOTW to original_contents. + std::string original_str = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + original_str += original_contents; + // Since WebCore now inserts a new HEAD element if there is no HEAD element + // when creating BODY element. (Please see HTMLParser::bodyCreateErrorCheck.) + // We need to append the HEAD content and corresponding META content if we + // find WebCore-generated HEAD element. + if (!doc.head().isNull()) { + WebString encoding = web_frame->encoding(); + std::string htmlTag("<html>"); + std::string::size_type pos = original_str.find(htmlTag); + ASSERT_NE(std::string::npos, pos); + pos += htmlTag.length(); + std::string head_part("<head>"); + head_part += + WebPageSerializer::generateMetaCharsetDeclaration(encoding).utf8(); + head_part += "</head>"; + original_str.insert(pos, head_part); + } + ASSERT_EQ(original_str, serialized_contents); +} + +// Test situation of html entities in attribute value when serializing +// HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEntitiesInAttributeValue) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII( + "dom_serializer/htmlentities_in_attribute_value.htm"); + // Get file URL. The URL is dummy URL to identify the following loading + // actions. The test content is in constant:original_contents. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Test contents. + static const char* const original_contents = + "<html><body title=\"&<>"'\"></body></html>"; + // Load the test contents. + LoadContents(original_contents, file_url, WebString()); + // Get value of BODY's title attribute in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_ele = doc.body(); + ASSERT_TRUE(!body_ele.isNull()); + WebString value = body_ele.getAttribute("title"); + ASSERT_TRUE(std::string(value.utf8()) == "&<>\"\'"); + // Do serialization. + SerializeDomForURL(file_url, false); + // Compare the serialized contents with original contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Compare the serialized contents with original contents to make sure + // they are same. + std::string original_str = + WebPageSerializer::generateMarkOfTheWebDeclaration(file_url).utf8(); + original_str += original_contents; + if (!doc.isNull()) { + WebString encoding = web_frame->encoding(); + std::string htmlTag("<html>"); + std::string::size_type pos = original_str.find(htmlTag); + ASSERT_NE(std::string::npos, pos); + pos += htmlTag.length(); + std::string head_part("<head>"); + head_part += + WebPageSerializer::generateMetaCharsetDeclaration(encoding).utf8(); + head_part += "</head>"; + original_str.insert(pos, head_part); + } + ASSERT_EQ(original_str, serialized_contents); +} + +// Test situation of non-standard HTML entities when serializing HTML DOM. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithNonStandardEntities) { + // Make a test file URL and load it. + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("nonstandard_htmlentities.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + LoadPageFromURL(file_url); + + // Get value of BODY's title attribute in DOM. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement body_element = doc.body(); + // Unescaped string for "%⊅&supl;'". + static const wchar_t parsed_value[] = { + '%', 0x2285, 0x00b9, '\'', 0 + }; + WebString value = body_element.getAttribute("title"); + ASSERT_TRUE(UTF16ToWide(value) == parsed_value); + ASSERT_TRUE(UTF16ToWide(body_element.innerText()) == parsed_value); + + // Do serialization. + SerializeDomForURL(file_url, false); + // Check the serialized string. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + // Confirm that the serialized string has no non-standard HTML entities. + ASSERT_EQ(std::string::npos, serialized_contents.find("%")); + ASSERT_EQ(std::string::npos, serialized_contents.find("⊅")); + ASSERT_EQ(std::string::npos, serialized_contents.find("&supl;")); + ASSERT_EQ(std::string::npos, serialized_contents.find("'")); +} + +// Test situation of BASE tag in original document when serializing HTML DOM. +// When serializing, we should comment the BASE tag, append a new BASE tag. +// rewrite all the savable URLs to relative local path, and change other URLs +// to absolute URLs. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithBaseTag) { + // There are total 2 available base tags in this test file. + const int kTotalBaseTagCountInTestFile = 2; + + FilePath page_file_path = data_dir_.AppendASCII("dom_serializer"); + file_util::EnsureEndsWithSeparator(&page_file_path); + + // Get page dir URL which is base URL of this file. + GURL path_dir_url = net::FilePathToFileURL(page_file_path); + // Get file path. + page_file_path = + page_file_path.AppendASCII("html_doc_has_base_tag.htm"); + // Get file URL. + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Since for this test, we assume there is no savable sub-resource links for + // this test file, also all links are relative URLs in this test file, so we + // need to check those relative URLs and make sure document has BASE tag. + WebFrame* web_frame = FindSubFrameByURL(test_shell_->webView(), file_url); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + // Go through all descent nodes. + WebNodeCollection all = doc.all(); + int original_base_tag_count = 0; + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + if (element.hasTagName("base")) { + original_base_tag_count++; + } else { + // Get link. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull() && element.hasTagName("a")) { + value = element.getAttribute("href"); + if (value.isEmpty()) + value = WebString(); + } + // Each link is relative link. + if (!value.isNull()) { + GURL link(value.utf8()); + ASSERT_TRUE(link.scheme().empty()); + } + } + } + ASSERT_EQ(original_base_tag_count, kTotalBaseTagCountInTestFile); + // Make sure in original document, the base URL is not equal with the + // |path_dir_url|. + GURL original_base_url(doc.baseURL()); + ASSERT_NE(original_base_url, path_dir_url); + + // Do serialization. + SerializeDomForURL(file_url, false); + + // Load the serialized contents. + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + LoadContents(serialized_contents, file_url, + web_frame->encoding()); + + // Make sure all links are absolute URLs and doc there are some number of + // BASE tags in serialized HTML data. Each of those BASE tags have same base + // URL which is as same as URL of current test file. + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + // Go through all descent nodes. + all = doc.all(); + int new_base_tag_count = 0; + for (WebNode node = all.firstItem(); !node.isNull(); + node = all.nextItem()) { + if (!node.isElementNode()) + continue; + WebElement element = node.to<WebElement>(); + if (element.hasTagName("base")) { + new_base_tag_count++; + } else { + // Get link. + WebString value = + webkit_glue::GetSubResourceLinkFromElement(element); + if (value.isNull() && element.hasTagName("a")) { + value = element.getAttribute("href"); + if (value.isEmpty()) + value = WebString(); + } + // Each link is absolute link. + if (!value.isNull()) { + GURL link(std::string(value.utf8())); + ASSERT_FALSE(link.scheme().empty()); + } + } + } + // We have one more added BASE tag which is generated by JavaScript. + ASSERT_EQ(new_base_tag_count, original_base_tag_count + 1); + // Make sure in new document, the base URL is equal with the |path_dir_url|. + GURL new_base_url(doc.baseURL()); + ASSERT_EQ(new_base_url, path_dir_url); +} + +// Serializing page which has an empty HEAD tag. +TEST_F(DomSerializerTests, SerializeHTMLDOMWithEmptyHead) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("empty_head.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + + // Load the test html content. + static const char* const empty_head_contents = + "<html><head></head><body>hello world</body></html>"; + LoadContents(empty_head_contents, file_url, WebString()); + + // Make sure the head tag is empty. + WebFrame* web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + WebDocument doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + WebElement head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + ASSERT_TRUE(!head_element.hasChildNodes()); + ASSERT_TRUE(head_element.childNodes().length() == 0); + + // Do serialization. + SerializeDomForURL(file_url, false); + // Make sure the serialized contents have META ; + ASSERT_TRUE(HasSerializedFrame(file_url)); + const std::string& serialized_contents = + GetSerializedContentForFrame(file_url); + + // Reload serialized contents and make sure there is only one META tag. + LoadContents(serialized_contents, file_url, web_frame->encoding()); + web_frame = test_shell_->webView()->mainFrame(); + ASSERT_TRUE(web_frame != NULL); + doc = web_frame->document(); + ASSERT_TRUE(doc.isHTMLDocument()); + head_element = doc.head(); + ASSERT_TRUE(!head_element.isNull()); + ASSERT_TRUE(head_element.hasChildNodes()); + ASSERT_TRUE(head_element.childNodes().length() == 1); + WebNode meta_node = head_element.firstChild(); + ASSERT_TRUE(!meta_node.isNull()); + // Get meta charset info. + std::string charset_info; + ASSERT_TRUE(IsMetaElement(meta_node, charset_info)); + ASSERT_TRUE(!charset_info.empty()); + ASSERT_TRUE(charset_info == std::string(web_frame->encoding().utf8())); + + // Check the body's first node is text node and its contents are + // "hello world" + WebElement body_element = doc.body(); + ASSERT_TRUE(!body_element.isNull()); + WebNode text_node = body_element.firstChild(); + ASSERT_TRUE(text_node.isTextNode()); + WebString text_node_contents = text_node.nodeValue(); + ASSERT_TRUE(std::string(text_node_contents.utf8()) == "hello world"); +} + +// Test that we don't crash when the page contains an iframe that +// was handled as a download (http://crbug.com/42212). +TEST_F(DomSerializerTests, SerializeDocumentWithDownloadedIFrame) { + FilePath page_file_path = data_dir_; + page_file_path = page_file_path.AppendASCII("dom_serializer"); + page_file_path = page_file_path.AppendASCII("iframe-src-is-exe.htm"); + GURL file_url = net::FilePathToFileURL(page_file_path); + ASSERT_TRUE(file_url.SchemeIsFile()); + // Load the test file. + LoadPageFromURL(file_url); + // Do a recursive serialization. We pass if we don't crash. + SerializeDomForURL(file_url, true); +} + +} // namespace diff --git a/webkit/glue/form_data.h b/webkit/glue/form_data.h new file mode 100644 index 0000000..033cac3 --- /dev/null +++ b/webkit/glue/form_data.h @@ -0,0 +1,41 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_FORM_DATA_H__ +#define WEBKIT_GLUE_FORM_DATA_H__ + +#include <vector> + +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "webkit/glue/form_field.h" + +namespace webkit_glue { + +// Holds information about a form to be filled and/or submitted. +struct FormData { + // The name of the form. + string16 name; + // GET or POST. + string16 method; + // The URL (minus query parameters) containing the form. + GURL origin; + // The action target of the form. + GURL action; + // A vector of all the input fields in the form. + std::vector<FormField> fields; + + // Used by FormStructureTest. + inline bool operator==(const FormData& form) const { + return (name == form.name && + StringToLowerASCII(method) == StringToLowerASCII(form.method) && + origin == form.origin && + action == form.action && + fields == form.fields); + } +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_DATA_H__ diff --git a/webkit/glue/form_field.cc b/webkit/glue/form_field.cc new file mode 100644 index 0000000..a0fbdef --- /dev/null +++ b/webkit/glue/form_field.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2010 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 "webkit/glue/form_field.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebOptionElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSelectElement.h" + +using WebKit::WebFormControlElement; +using WebKit::WebElement; +using WebKit::WebInputElement; +using WebKit::WebOptionElement; +using WebKit::WebSelectElement; +using WebKit::WebVector; + +namespace webkit_glue { + +FormField::FormField() + : size_(0) { +} + +// TODO(jhawkins): This constructor should probably be deprecated and the +// functionality moved to FormManager. +FormField::FormField(WebFormControlElement element) + : size_(0) { + name_ = element.nameForAutofill(); + + // TODO(jhawkins): Extract the field label. For now we just use the field + // name. + label_ = name_; + + form_control_type_ = element.formControlType(); + if (form_control_type_ == ASCIIToUTF16("text")) { + const WebInputElement& input_element = element.toConst<WebInputElement>(); + value_ = input_element.value(); + size_ = input_element.size(); + } else if (form_control_type_ == ASCIIToUTF16("select-one")) { + WebSelectElement select_element = element.to<WebSelectElement>(); + value_ = select_element.value(); + + // For select-one elements copy option strings. + WebVector<WebElement> list_items = select_element.listItems(); + option_strings_.reserve(list_items.size()); + for (size_t i = 0; i < list_items.size(); ++i) { + if (list_items[i].hasTagName("option")) + option_strings_.push_back(list_items[i].to<WebOptionElement>().value()); + } + } + + TrimWhitespace(value_, TRIM_LEADING, &value_); +} + +FormField::FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size) + : label_(label), + name_(name), + value_(value), + form_control_type_(form_control_type), + size_(size) { +} + +bool FormField::operator==(const FormField& field) const { + // A FormField stores a value, but the value is not part of the identity of + // the field, so we don't want to compare the values. + return (label_ == field.label_ && + name_ == field.name_ && + form_control_type_ == field.form_control_type_ && + size_ == field.size_); +} + +bool FormField::operator!=(const FormField& field) const { + return !operator==(field); +} + +bool FormField::StrictlyEqualsHack(const FormField& field) const { + return (label_ == field.label_ && + name_ == field.name_ && + value_ == field.value_ && + form_control_type_ == field.form_control_type_ && + size_ == field.size_); +} + +std::ostream& operator<<(std::ostream& os, const FormField& field) { + return os + << UTF16ToUTF8(field.label()) + << " " + << UTF16ToUTF8(field.name()) + << " " + << UTF16ToUTF8(field.value()) + << " " + << UTF16ToUTF8(field.form_control_type()) + << " " + << field.size(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/form_field.h b/webkit/glue/form_field.h new file mode 100644 index 0000000..2a1ffcf --- /dev/null +++ b/webkit/glue/form_field.h @@ -0,0 +1,73 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_FORM_FIELD_H_ +#define WEBKIT_GLUE_FORM_FIELD_H_ + +#include <vector> + +#include "base/string16.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFormControlElement.h" + +namespace webkit_glue { + +// Stores information about a field in a form. +class FormField { + public: + FormField(); + explicit FormField(WebKit::WebFormControlElement element); + FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size); + + const string16& label() const { return label_; } + const string16& name() const { return name_; } + const string16& value() const { return value_; } + const string16& form_control_type() const { return form_control_type_; } + int size() const { return size_; } + // Returns option string for elements for which they make sense (select-one, + // for example) for the rest of elements return an empty array. + const std::vector<string16>& option_strings() const { + return option_strings_; + } + + void set_label(const string16& label) { label_ = label; } + void set_name(const string16& name) { name_ = name; } + void set_value(const string16& value) { value_ = value; } + void set_form_control_type(const string16& form_control_type) { + form_control_type_ = form_control_type; + } + void set_size(int size) { size_ = size; } + void set_option_strings(const std::vector<string16>& strings) { + option_strings_ = strings; + } + + // Equality tests for identity which does not include |value_| or |size_|. + // Use |StrictlyEqualsHack| method to test all members. + // TODO(dhollowa): These operators need to be revised when we implement field + // ids. + bool operator==(const FormField& field) const; + bool operator!=(const FormField& field) const; + + // Test equality of all data members. + // TODO(dhollowa): This will be removed when we implement field ids. + bool StrictlyEqualsHack(const FormField& field) const; + + private: + string16 label_; + string16 name_; + string16 value_; + string16 form_control_type_; + int size_; + std::vector<string16> option_strings_; +}; + +// So we can compare FormFields with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const FormField& profile); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_FIELD_H_ diff --git a/webkit/glue/ftp_directory_listing_response_delegate.cc b/webkit/glue/ftp_directory_listing_response_delegate.cc new file mode 100644 index 0000000..80737db --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/ftp_directory_listing_response_delegate.h" + +#include <vector> + +#include "base/i18n/icu_encoding_detection.h" +#include "base/i18n/icu_string_conversions.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "base/time.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/ftp/ftp_directory_listing_parser.h" +#include "net/ftp/ftp_server_type_histograms.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" + +using net::FtpDirectoryListingEntry; + +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLResponse; + +namespace { + +string16 ConvertPathToUTF16(const std::string& path) { + // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII, + // but many old FTP servers use legacy encodings. Try UTF-8 first. + if (IsStringUTF8(path)) + return UTF8ToUTF16(path); + + // Try detecting the encoding. The sample is rather small though, so it may + // fail. + std::string encoding; + if (base::DetectEncoding(path, &encoding) && !encoding.empty()) { + string16 path_utf16; + if (base::CodepageToUTF16(path, encoding.c_str(), + base::OnStringConversionError::SUBSTITUTE, + &path_utf16)) { + return path_utf16; + } + } + + // Use system native encoding as the last resort. + return WideToUTF16Hack(base::SysNativeMBToWide(path)); +} + +} // namespace + +namespace webkit_glue { + +FtpDirectoryListingResponseDelegate::FtpDirectoryListingResponseDelegate( + WebURLLoaderClient* client, + WebURLLoader* loader, + const WebURLResponse& response) + : client_(client), + loader_(loader), + original_response_(response), + buffer_(base::Time::Now()), + updated_histograms_(false), + had_parsing_error_(false) { + Init(); +} + +void FtpDirectoryListingResponseDelegate::OnReceivedData(const char* data, + int data_len) { + if (had_parsing_error_) + return; + + if (buffer_.ConsumeData(data, data_len) == net::OK) + ProcessReceivedEntries(); + else + had_parsing_error_ = true; +} + +void FtpDirectoryListingResponseDelegate::OnCompletedRequest() { + if (!had_parsing_error_ && buffer_.ProcessRemainingData() == net::OK) + ProcessReceivedEntries(); + else + had_parsing_error_ = true; + + if (had_parsing_error_) + SendDataToClient("<script>onListingParsingError();</script>\n"); +} + +void FtpDirectoryListingResponseDelegate::Init() { + GURL response_url(original_response_.url()); + UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | + UnescapeRule::URL_SPECIAL_CHARS; + std::string unescaped_path = UnescapeURLComponent(response_url.path(), + unescape_rules); + SendDataToClient(net::GetDirectoryListingHeader( + ConvertPathToUTF16(unescaped_path))); + + // If this isn't top level directory (i.e. the path isn't "/",) + // add a link to the parent directory. + if (response_url.path().length() > 1) { + SendDataToClient(net::GetDirectoryListingEntry( + ASCIIToUTF16(".."), std::string(), false, 0, base::Time())); + } +} + +bool FtpDirectoryListingResponseDelegate::ConvertToServerEncoding( + const string16& filename, std::string* raw_bytes) const { + if (buffer_.encoding().empty()) { + *raw_bytes = std::string(); + return true; + } + + return base::UTF16ToCodepage(filename, buffer_.encoding().c_str(), + base::OnStringConversionError::FAIL, + raw_bytes); +} + +void FtpDirectoryListingResponseDelegate::ProcessReceivedEntries() { + if (!updated_histograms_ && buffer_.EntryAvailable()) { + // Only log the server type if we got enough data to reliably detect it. + net::UpdateFtpServerTypeHistograms(buffer_.GetServerType()); + updated_histograms_ = true; + } + + while (buffer_.EntryAvailable()) { + FtpDirectoryListingEntry entry = buffer_.PopEntry(); + + // Skip the current and parent directory entries in the listing. Our header + // always includes them. + if (EqualsASCII(entry.name, ".") || EqualsASCII(entry.name, "..")) + continue; + + bool is_directory = (entry.type == FtpDirectoryListingEntry::DIRECTORY); + int64 size = entry.size; + if (entry.type != FtpDirectoryListingEntry::FILE) + size = 0; + std::string raw_bytes; + if (ConvertToServerEncoding(entry.name, &raw_bytes)) { + SendDataToClient(net::GetDirectoryListingEntry( + entry.name, raw_bytes, is_directory, size, entry.last_modified)); + } else { + // Consider an encoding problem a non-fatal error. The server's support + // for non-ASCII characters might be buggy. Display an error message, + // but keep trying to display the rest of the listing (most file names + // are ASCII anyway, we could be just unlucky with this one). + had_parsing_error_ = true; + } + } +} + +void FtpDirectoryListingResponseDelegate::SendDataToClient( + const std::string& data) { + client_->didReceiveData(loader_, data.data(), data.length()); +} + +} // namespace webkit_glue diff --git a/webkit/glue/ftp_directory_listing_response_delegate.h b/webkit/glue/ftp_directory_listing_response_delegate.h new file mode 100644 index 0000000..1218da9 --- /dev/null +++ b/webkit/glue/ftp_directory_listing_response_delegate.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A delegate class of WebURLLoaderImpl that handles text/vnd.chromium.ftp-dir +// data. + +#ifndef WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ +#define WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ + +#include <string> + +#include "net/ftp/ftp_directory_listing_buffer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +namespace WebKit { +class WebURLLoader; +class WebURLLoaderClient; +} + +namespace webkit_glue { + +class FtpDirectoryListingResponseDelegate { + public: + FtpDirectoryListingResponseDelegate(WebKit::WebURLLoaderClient* client, + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + + // Passed through from ResourceHandleInternal + void OnReceivedData(const char* data, int data_len); + void OnCompletedRequest(); + + private: + void Init(); + + // Converts |filename| to detected server encoding and puts the result + // in |raw_bytes| (if no conversion is necessary, an empty string is used). + // Returns true on success. + bool ConvertToServerEncoding(const string16& filename, + std::string* raw_bytes) const; + + // Fetches the listing entries from the buffer and sends them to the client. + void ProcessReceivedEntries(); + + void SendDataToClient(const std::string& data); + + // Pointers to the client and associated loader so we can make callbacks as + // we parse pieces of data. + WebKit::WebURLLoaderClient* client_; + WebKit::WebURLLoader* loader_; + + // The original resource response for this request. We use this as a + // starting point for each parts response. + WebKit::WebURLResponse original_response_; + + // Data buffer also responsible for parsing the listing data. + net::FtpDirectoryListingBuffer buffer_; + + // True if we updated histogram data (we only want to do it once). + bool updated_histograms_; + + // True if we got an error when parsing the response. + bool had_parsing_error_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FTP_DIRECTORY_LISTING_RESPONSE_DELEGATE_H_ diff --git a/webkit/glue/glue_serialize.cc b/webkit/glue/glue_serialize.cc new file mode 100644 index 0000000..caf37b4 --- /dev/null +++ b/webkit/glue/glue_serialize.cc @@ -0,0 +1,473 @@ +// Copyright (c) 2010 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 "webkit/glue/glue_serialize.h" + +#include <string> + +#include "base/pickle.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHistoryItem.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSerializedScriptValue.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebData; +using WebKit::WebFileInfo; +using WebKit::WebHistoryItem; +using WebKit::WebHTTPBody; +using WebKit::WebPoint; +using WebKit::WebSerializedScriptValue; +using WebKit::WebString; +using WebKit::WebUChar; +using WebKit::WebVector; + +namespace webkit_glue { + +struct SerializeObject { + SerializeObject() : iter(NULL) {} + SerializeObject(const char* data, int len) : pickle(data, len), iter(NULL) {} + + std::string GetAsString() { + return std::string(static_cast<const char*>(pickle.data()), pickle.size()); + } + + Pickle pickle; + mutable void* iter; + mutable int version; +}; + +// TODO(mpcomplete): obsolete versions 1 and 2 after 1/1/2008. +// Version ID used in reading/writing history items. +// 1: Initial revision. +// 2: Added case for NULL string versus "". Version 2 code can read Version 1 +// data, but not vice versa. +// 3: Version 2 was broken, it stored number of WebUChars, not number of bytes. +// This version checks and reads v1 and v2 correctly. +// 4: Adds support for storing FormData::identifier(). +// 5: Adds support for empty FormData +// 6: Adds support for documentSequenceNumbers +// 7: Adds support for stateObject +// 8: Adds support for file range and modification time +// 9: Adds support for itemSequenceNumbers +// Should be const, but unit tests may modify it. +// +// NOTE: If the version is -1, then the pickle contains only a URL string. +// See CreateHistoryStateForURL. +// +int kVersion = 9; + +// A bunch of convenience functions to read/write to SerializeObjects. +// The serializers assume the input data is in the correct format and so does +// no error checking. +inline void WriteData(const void* data, int length, SerializeObject* obj) { + obj->pickle.WriteData(static_cast<const char*>(data), length); +} + +inline void ReadData(const SerializeObject* obj, const void** data, + int* length) { + const char* tmp = NULL; + obj->pickle.ReadData(&obj->iter, &tmp, length); + *data = tmp; +} + +inline bool ReadBytes(const SerializeObject* obj, const void** data, + int length) { + const char *tmp; + if (!obj->pickle.ReadBytes(&obj->iter, &tmp, length)) + return false; + *data = tmp; + return true; +} + +inline void WriteInteger(int data, SerializeObject* obj) { + obj->pickle.WriteInt(data); +} + +inline int ReadInteger(const SerializeObject* obj) { + int tmp = 0; + obj->pickle.ReadInt(&obj->iter, &tmp); + return tmp; +} + +inline void WriteInteger64(int64 data, SerializeObject* obj) { + obj->pickle.WriteInt64(data); +} + +inline int64 ReadInteger64(const SerializeObject* obj) { + int64 tmp = 0; + obj->pickle.ReadInt64(&obj->iter, &tmp); + return tmp; +} + +inline void WriteReal(double data, SerializeObject* obj) { + WriteData(&data, sizeof(double), obj); +} + +inline double ReadReal(const SerializeObject* obj) { + const void* tmp; + int length = 0; + ReadData(obj, &tmp, &length); + if (length > 0 && length >= static_cast<int>(sizeof(0.0))) + return *static_cast<const double*>(tmp); + else + return 0.0; +} + +inline void WriteBoolean(bool data, SerializeObject* obj) { + obj->pickle.WriteInt(data ? 1 : 0); +} + +inline bool ReadBoolean(const SerializeObject* obj) { + bool tmp = false; + obj->pickle.ReadBool(&obj->iter, &tmp); + return tmp; +} + +inline void WriteGURL(const GURL& url, SerializeObject* obj) { + obj->pickle.WriteString(url.possibly_invalid_spec()); +} + +inline GURL ReadGURL(const SerializeObject* obj) { + std::string spec; + obj->pickle.ReadString(&obj->iter, &spec); + return GURL(spec); +} + +// Read/WriteString pickle the WebString as <int length><WebUChar* data>. +// If length == -1, then the WebString itself is NULL (WebString()). +// Otherwise the length is the number of WebUChars (not bytes) in the WebString. +inline void WriteString(const WebString& str, SerializeObject* obj) { + switch (kVersion) { + case 1: + // Version 1 writes <length in bytes><string data>. + // It saves WebString() and "" as "". + obj->pickle.WriteInt(str.length() * sizeof(WebUChar)); + obj->pickle.WriteBytes(str.data(), str.length() * sizeof(WebUChar)); + break; + case 2: + // Version 2 writes <length in WebUChar><string data>. + // It uses -1 in the length field to mean WebString(). + if (str.isNull()) { + obj->pickle.WriteInt(-1); + } else { + obj->pickle.WriteInt(str.length()); + obj->pickle.WriteBytes(str.data(), + str.length() * sizeof(WebUChar)); + } + break; + default: + // Version 3+ writes <length in bytes><string data>. + // It uses -1 in the length field to mean WebString(). + if (str.isNull()) { + obj->pickle.WriteInt(-1); + } else { + obj->pickle.WriteInt(str.length() * sizeof(WebUChar)); + obj->pickle.WriteBytes(str.data(), + str.length() * sizeof(WebUChar)); + } + break; + } +} + +// This reads a serialized WebString from obj. If a string can't be read, +// WebString() is returned. +inline WebString ReadString(const SerializeObject* obj) { + int length; + + // Versions 1, 2, and 3 all start with an integer. + if (!obj->pickle.ReadInt(&obj->iter, &length)) + return WebString(); + + // Starting with version 2, -1 means WebString(). + if (length == -1) + return WebString(); + + // In version 2, the length field was the length in WebUChars. + // In version 1 and 3 it is the length in bytes. + int bytes = ((obj->version == 2) ? length * sizeof(WebUChar) : length); + + const void* data; + if (!ReadBytes(obj, &data, bytes)) + return WebString(); + return WebString(static_cast<const WebUChar*>(data), + bytes / sizeof(WebUChar)); +} + +// Writes a Vector of Strings into a SerializeObject for serialization. +static void WriteStringVector( + const WebVector<WebString>& data, SerializeObject* obj) { + WriteInteger(static_cast<int>(data.size()), obj); + for (size_t i = 0, c = data.size(); i < c; ++i) { + unsigned ui = static_cast<unsigned>(i); // sigh + WriteString(data[ui], obj); + } +} + +static WebVector<WebString> ReadStringVector(const SerializeObject* obj) { + int num_elements = ReadInteger(obj); + WebVector<WebString> result(static_cast<size_t>(num_elements)); + for (int i = 0; i < num_elements; ++i) + result[i] = ReadString(obj); + return result; +} + +// Writes a FormData object into a SerializeObject for serialization. +static void WriteFormData(const WebHTTPBody& http_body, SerializeObject* obj) { + WriteBoolean(!http_body.isNull(), obj); + + if (http_body.isNull()) + return; + + WriteInteger(static_cast<int>(http_body.elementCount()), obj); + WebHTTPBody::Element element; + for (size_t i = 0; http_body.elementAt(i, element); ++i) { + WriteInteger(element.type, obj); + if (element.type == WebHTTPBody::Element::TypeData) { + WriteData(element.data.data(), static_cast<int>(element.data.size()), + obj); + } else { + WriteString(element.filePath, obj); + WriteInteger64(element.fileStart, obj); + WriteInteger64(element.fileLength, obj); + WriteReal(element.fileInfo.modificationTime, obj); + } + } + WriteInteger64(http_body.identifier(), obj); +} + +static WebHTTPBody ReadFormData(const SerializeObject* obj) { + // In newer versions, an initial boolean indicates if we have form data. + if (obj->version >= 5 && !ReadBoolean(obj)) + return WebHTTPBody(); + + // In older versions, 0 elements implied no form data. + int num_elements = ReadInteger(obj); + if (num_elements == 0 && obj->version < 5) + return WebHTTPBody(); + + WebHTTPBody http_body; + http_body.initialize(); + + for (int i = 0; i < num_elements; ++i) { + int type = ReadInteger(obj); + if (type == WebHTTPBody::Element::TypeData) { + const void* data; + int length = -1; + ReadData(obj, &data, &length); + if (length >= 0) + http_body.appendData(WebData(static_cast<const char*>(data), length)); + } else { + WebString file_path = ReadString(obj); + long long file_start = 0; + long long file_length = -1; + WebFileInfo file_info; + if (obj->version >= 8) { + file_start = ReadInteger64(obj); + file_length = ReadInteger64(obj); + file_info.modificationTime = ReadReal(obj); + } + http_body.appendFileRange(file_path, file_start, file_length, file_info); + } + } + if (obj->version >= 4) + http_body.setIdentifier(ReadInteger64(obj)); + + return http_body; +} + +// Writes the HistoryItem data into the SerializeObject object for +// serialization. +static void WriteHistoryItem( + const WebHistoryItem& item, SerializeObject* obj) { + // WARNING: This data may be persisted for later use. As such, care must be + // taken when changing the serialized format. If a new field needs to be + // written, only adding at the end will make it easier to deal with loading + // older versions. Similarly, this should NOT save fields with sensitive + // data, such as password fields. + WriteInteger(kVersion, obj); + WriteString(item.urlString(), obj); + WriteString(item.originalURLString(), obj); + WriteString(item.target(), obj); + WriteString(item.parent(), obj); + WriteString(item.title(), obj); + WriteString(item.alternateTitle(), obj); + WriteReal(item.lastVisitedTime(), obj); + WriteInteger(item.scrollOffset().x, obj); + WriteInteger(item.scrollOffset().y, obj); + WriteBoolean(item.isTargetItem(), obj); + WriteInteger(item.visitCount(), obj); + WriteString(item.referrer(), obj); + + WriteStringVector(item.documentState(), obj); + + if (kVersion >= 9) + WriteInteger64(item.itemSequenceNumber(), obj); + if (kVersion >= 6) + WriteInteger64(item.documentSequenceNumber(), obj); + if (kVersion >= 7) { + bool has_state_object = !item.stateObject().isNull(); + WriteBoolean(has_state_object, obj); + if (has_state_object) + WriteString(item.stateObject().toString(), obj); + } + + // Yes, the referrer is written twice. This is for backwards + // compatibility with the format. + WriteFormData(item.httpBody(), obj); + WriteString(item.httpContentType(), obj); + WriteString(item.referrer(), obj); + + // Subitems + const WebVector<WebHistoryItem>& children = item.children(); + WriteInteger(static_cast<int>(children.size()), obj); + for (size_t i = 0, c = children.size(); i < c; ++i) + WriteHistoryItem(children[i], obj); +} + +// Creates a new HistoryItem tree based on the serialized string. +// Assumes the data is in the format returned by WriteHistoryItem. +static WebHistoryItem ReadHistoryItem( + const SerializeObject* obj, bool include_form_data) { + // See note in WriteHistoryItem. on this. + obj->version = ReadInteger(obj); + + if (obj->version == -1) { + GURL url = ReadGURL(obj); + WebHistoryItem item; + item.initialize(); + item.setURLString(WebString::fromUTF8(url.possibly_invalid_spec())); + return item; + } + + if (obj->version > kVersion || obj->version < 1) + return WebHistoryItem(); + + WebHistoryItem item; + item.initialize(); + + item.setURLString(ReadString(obj)); + item.setOriginalURLString(ReadString(obj)); + item.setTarget(ReadString(obj)); + item.setParent(ReadString(obj)); + item.setTitle(ReadString(obj)); + item.setAlternateTitle(ReadString(obj)); + item.setLastVisitedTime(ReadReal(obj)); + int x = ReadInteger(obj); + int y = ReadInteger(obj); + item.setScrollOffset(WebPoint(x, y)); + item.setIsTargetItem(ReadBoolean(obj)); + item.setVisitCount(ReadInteger(obj)); + item.setReferrer(ReadString(obj)); + + item.setDocumentState(ReadStringVector(obj)); + + if (obj->version >= 9) + item.setItemSequenceNumber(ReadInteger64(obj)); + if (obj->version >= 6) + item.setDocumentSequenceNumber(ReadInteger64(obj)); + if (obj->version >= 7) { + bool has_state_object = ReadBoolean(obj); + if (has_state_object) { + item.setStateObject( + WebSerializedScriptValue::fromString(ReadString(obj))); + } + } + + // The extra referrer string is read for backwards compat. + const WebHTTPBody& http_body = ReadFormData(obj); + const WebString& http_content_type = ReadString(obj); + ALLOW_UNUSED const WebString& unused_referrer = ReadString(obj); + if (include_form_data) { + item.setHTTPBody(http_body); + item.setHTTPContentType(http_content_type); + } + + // Subitems + int num_children = ReadInteger(obj); + for (int i = 0; i < num_children; ++i) + item.appendToChildren(ReadHistoryItem(obj, include_form_data)); + + return item; +} + +// Serialize a HistoryItem to a string, using our JSON Value serializer. +std::string HistoryItemToString(const WebHistoryItem& item) { + if (item.isNull()) + return std::string(); + + SerializeObject obj; + WriteHistoryItem(item, &obj); + return obj.GetAsString(); +} + +// Reconstruct a HistoryItem from a string, using our JSON Value deserializer. +// This assumes that the given serialized string has all the required key,value +// pairs, and does minimal error checking. If |include_form_data| is true, +// the form data from a post is restored, otherwise the form data is empty. +static WebHistoryItem HistoryItemFromString( + const std::string& serialized_item, + bool include_form_data) { + if (serialized_item.empty()) + return WebHistoryItem(); + + SerializeObject obj(serialized_item.data(), + static_cast<int>(serialized_item.length())); + return ReadHistoryItem(&obj, include_form_data); +} + +WebHistoryItem HistoryItemFromString( + const std::string& serialized_item) { + return HistoryItemFromString(serialized_item, true); +} + +// For testing purposes only. +void HistoryItemToVersionedString(const WebHistoryItem& item, int version, + std::string* serialized_item) { + if (item.isNull()) { + serialized_item->clear(); + return; + } + + // Temporarily change the version. + int real_version = kVersion; + kVersion = version; + + SerializeObject obj; + WriteHistoryItem(item, &obj); + *serialized_item = obj.GetAsString(); + + kVersion = real_version; +} + +std::string CreateHistoryStateForURL(const GURL& url) { + // We avoid using the WebKit API here, so that we do not need to have WebKit + // initialized before calling this method. Instead, we write a simple + // serialization of the given URL with a dummy version number of -1. This + // will be interpreted by ReadHistoryItem as a request to create a default + // WebHistoryItem. + SerializeObject obj; + WriteInteger(-1, &obj); + WriteGURL(url, &obj); + return obj.GetAsString(); +} + +std::string RemoveFormDataFromHistoryState(const std::string& content_state) { + // TODO(darin): We should avoid using the WebKit API here, so that we do not + // need to have WebKit initialized before calling this method. + const WebHistoryItem& item = HistoryItemFromString(content_state, false); + if (item.isNull()) { + // Couldn't parse the string, return an empty string. + return std::string(); + } + + return HistoryItemToString(item); +} + +} // namespace webkit_glue diff --git a/webkit/glue/glue_serialize.h b/webkit/glue/glue_serialize.h new file mode 100644 index 0000000..303fe3a --- /dev/null +++ b/webkit/glue/glue_serialize.h @@ -0,0 +1,31 @@ +// Copyright (c) 2006-2008 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. +// +// This file contains (de)serialization (or if you like python, pickling) +// methods for various objects that we want to persist. +// In serialization, we write an object's state to a string in some opaque +// format. Deserialization reconstructs the object's state from such a string. + +#ifndef WEBKIT_GLUE_GLUE_SERIALIZE_H_ +#define WEBKIT_GLUE_GLUE_SERIALIZE_H_ + +#include <string> +#include "third_party/WebKit/WebKit/chromium/public/WebHistoryItem.h" + +namespace webkit_glue { + +// HistoryItem serialization. +std::string HistoryItemToString( + const WebKit::WebHistoryItem& item); +WebKit::WebHistoryItem HistoryItemFromString( + const std::string& serialized_item); + +// For testing purposes only. +void HistoryItemToVersionedString( + const WebKit::WebHistoryItem& item, int version, + std::string* serialized_item); + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_GLUE_SERIALIZE_H_ diff --git a/webkit/glue/glue_serialize_unittest.cc b/webkit/glue/glue_serialize_unittest.cc new file mode 100644 index 0000000..34ce09f --- /dev/null +++ b/webkit/glue/glue_serialize_unittest.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "base/pickle.h" +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/glue_serialize.h" + +using WebKit::WebData; +using WebKit::WebFileInfo; +using WebKit::WebHistoryItem; +using WebKit::WebHTTPBody; +using WebKit::WebPoint; +using WebKit::WebString; +using WebKit::WebUChar; +using WebKit::WebVector; + +namespace { +class GlueSerializeTest : public testing::Test { + public: + // Makes a FormData with some random data. + WebHTTPBody MakeFormData() { + WebHTTPBody http_body; + http_body.initialize(); + + const char d1[] = "first data block"; + http_body.appendData(WebData(d1, sizeof(d1)-1)); + + http_body.appendFile(WebString::fromUTF8("file.txt")); + + const char d2[] = "data the second"; + http_body.appendData(WebData(d2, sizeof(d2)-1)); + + return http_body; + } + + // Constructs a HistoryItem with some random data and an optional child. + WebHistoryItem MakeHistoryItem(bool with_form_data, bool pregnant) { + WebHistoryItem item; + item.initialize(); + + item.setURLString(WebString::fromUTF8("urlString")); + item.setOriginalURLString(WebString::fromUTF8("originalURLString")); + item.setTarget(WebString::fromUTF8("target")); + item.setParent(WebString::fromUTF8("parent")); + item.setTitle(WebString::fromUTF8("title")); + item.setAlternateTitle(WebString::fromUTF8("alternateTitle")); + item.setLastVisitedTime(13.37); + item.setScrollOffset(WebPoint(42, -42)); + item.setIsTargetItem(true); + item.setVisitCount(42*42); + + WebVector<WebString> document_state(size_t(3)); + document_state[0] = WebString::fromUTF8("state1"); + document_state[1] = WebString::fromUTF8("state2"); + document_state[2] = WebString::fromUTF8("state AWESOME"); + item.setDocumentState(document_state); + + // Form Data + if (with_form_data) { + item.setHTTPBody(MakeFormData()); + item.setHTTPContentType(WebString::fromUTF8("formContentType")); + } + + // Setting the FormInfo causes the referrer to be set, so we set the + // referrer after setting the form info. + item.setReferrer(WebString::fromUTF8("referrer")); + + // Children + if (pregnant) + item.appendToChildren(MakeHistoryItem(false, false)); + + return item; + } + + // Checks that a == b. + void HistoryItemExpectEqual(const WebHistoryItem& a, + const WebHistoryItem& b) { + EXPECT_EQ(string16(a.urlString()), string16(b.urlString())); + EXPECT_EQ(string16(a.originalURLString()), string16(b.originalURLString())); + EXPECT_EQ(string16(a.target()), string16(b.target())); + EXPECT_EQ(string16(a.parent()), string16(b.parent())); + EXPECT_EQ(string16(a.title()), string16(b.title())); + EXPECT_EQ(string16(a.alternateTitle()), string16(b.alternateTitle())); + EXPECT_EQ(a.lastVisitedTime(), b.lastVisitedTime()); + EXPECT_EQ(a.scrollOffset(), b.scrollOffset()); + EXPECT_EQ(a.isTargetItem(), b.isTargetItem()); + EXPECT_EQ(a.visitCount(), b.visitCount()); + EXPECT_EQ(string16(a.referrer()), string16(b.referrer())); + + const WebVector<WebString>& a_docstate = a.documentState(); + const WebVector<WebString>& b_docstate = b.documentState(); + EXPECT_EQ(a_docstate.size(), b_docstate.size()); + for (size_t i = 0, c = a_docstate.size(); i < c; ++i) + EXPECT_EQ(string16(a_docstate[i]), string16(b_docstate[i])); + + // Form Data + const WebHTTPBody& a_body = a.httpBody(); + const WebHTTPBody& b_body = b.httpBody(); + EXPECT_EQ(!a_body.isNull(), !b_body.isNull()); + if (!a_body.isNull() && !b_body.isNull()) { + EXPECT_EQ(a_body.elementCount(), b_body.elementCount()); + WebHTTPBody::Element a_elem, b_elem; + for (size_t i = 0; a_body.elementAt(i, a_elem) && + b_body.elementAt(i, b_elem); ++i) { + EXPECT_EQ(a_elem.type, b_elem.type); + if (a_elem.type == WebHTTPBody::Element::TypeData) { + EXPECT_EQ(std::string(a_elem.data.data(), a_elem.data.size()), + std::string(b_elem.data.data(), b_elem.data.size())); + } else { + EXPECT_EQ(string16(a_elem.filePath), string16(b_elem.filePath)); + } + } + } + EXPECT_EQ(string16(a.httpContentType()), string16(b.httpContentType())); + + // Children + const WebVector<WebHistoryItem>& a_children = a.children(); + const WebVector<WebHistoryItem>& b_children = b.children(); + EXPECT_EQ(a_children.size(), b_children.size()); + for (size_t i = 0, c = a_children.size(); i < c; ++i) + HistoryItemExpectEqual(a_children[i], b_children[i]); + } +}; + +// Test old versions of serialized data to ensure that newer versions of code +// can still read history items written by previous versions. +TEST_F(GlueSerializeTest, BackwardsCompatibleTest) { + const WebHistoryItem& item = MakeHistoryItem(false, false); + + // Make sure version 3 (current version) can read versions 1 and 2. + for (int i = 1; i <= 2; i++) { + std::string serialized_item; + webkit_glue::HistoryItemToVersionedString(item, i, &serialized_item); + const WebHistoryItem& deserialized_item = + webkit_glue::HistoryItemFromString(serialized_item); + ASSERT_FALSE(item.isNull()); + ASSERT_FALSE(deserialized_item.isNull()); + HistoryItemExpectEqual(item, deserialized_item); + } +} + +// Makes sure that a HistoryItem remains intact after being serialized and +// deserialized. +TEST_F(GlueSerializeTest, HistoryItemSerializeTest) { + const WebHistoryItem& item = MakeHistoryItem(true, true); + const std::string& serialized_item = webkit_glue::HistoryItemToString(item); + const WebHistoryItem& deserialized_item = + webkit_glue::HistoryItemFromString(serialized_item); + + ASSERT_FALSE(item.isNull()); + ASSERT_FALSE(deserialized_item.isNull()); + HistoryItemExpectEqual(item, deserialized_item); +} + +// Checks that broken messages don't take out our process. +TEST_F(GlueSerializeTest, BadMessagesTest) { + { + Pickle p; + // Version 1 + p.WriteInt(1); + // Empty strings. + for (int i = 0; i < 6; ++i) + p.WriteInt(-1); + // Bad real number. + p.WriteInt(-1); + std::string s(static_cast<const char*>(p.data()), p.size()); + webkit_glue::HistoryItemFromString(s); + } + { + double d = 0; + Pickle p; + // Version 1 + p.WriteInt(1); + // Empty strings. + for (int i = 0; i < 6; ++i) + p.WriteInt(-1); + // More misc fields. + p.WriteData(reinterpret_cast<const char*>(&d), sizeof(d)); + p.WriteInt(1); + p.WriteInt(1); + p.WriteInt(0); + p.WriteInt(0); + p.WriteInt(-1); + p.WriteInt(0); + // WebForm + p.WriteInt(1); + p.WriteInt(WebHTTPBody::Element::TypeData); + std::string s(static_cast<const char*>(p.data()), p.size()); + webkit_glue::HistoryItemFromString(s); + } +} + +} // namespace diff --git a/webkit/glue/iframe_redirect_unittest.cc b/webkit/glue/iframe_redirect_unittest.cc new file mode 100644 index 0000000..9170d19 --- /dev/null +++ b/webkit/glue/iframe_redirect_unittest.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "base/file_util.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDataSource.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebDataSource; +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebVector; + +typedef TestShellTest IFrameRedirectTest; + +// Tests that loading a page in an iframe from javascript results in +// a redirect from about:blank. +TEST_F(IFrameRedirectTest, Test) { + FilePath iframes_data_dir_ = data_dir_; + iframes_data_dir_ = iframes_data_dir_.AppendASCII("test_shell"); + iframes_data_dir_ = iframes_data_dir_.AppendASCII("iframe_redirect"); + ASSERT_TRUE(file_util::PathExists(iframes_data_dir_)); + + GURL test_url = GetTestURL(iframes_data_dir_, "main.html"); + + test_shell_->LoadURL(test_url); + test_shell_->WaitTestFinished(); + + WebFrame* iframe = + test_shell_->webView()->findFrameByName(WebString::fromUTF8("ifr")); + ASSERT_TRUE(iframe != NULL); + WebDataSource* iframe_ds = iframe->dataSource(); + ASSERT_TRUE(iframe_ds != NULL); + WebVector<WebURL> redirects; + iframe_ds->redirectChain(redirects); + ASSERT_FALSE(redirects.isEmpty()); + ASSERT_TRUE(GURL(redirects[0]) == GURL("about:blank")); +} diff --git a/webkit/glue/image_decoder.cc b/webkit/glue/image_decoder.cc new file mode 100644 index 0000000..6850909 --- /dev/null +++ b/webkit/glue/image_decoder.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/image_decoder.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/skia/include/core/SkBitmap.h" + +#if WEBKIT_USING_CG +#include "skia/ext/skia_utils_mac.h" +#endif + +using WebKit::WebData; +using WebKit::WebImage; + +namespace webkit_glue { + +ImageDecoder::ImageDecoder() : desired_icon_size_(0, 0) { +} + +ImageDecoder::ImageDecoder(const gfx::Size& desired_icon_size) + : desired_icon_size_(desired_icon_size) { +} + +ImageDecoder::~ImageDecoder() { +} + +SkBitmap ImageDecoder::Decode(const unsigned char* data, size_t size) const { + const WebImage& image = WebImage::fromData( + WebData(reinterpret_cast<const char*>(data), size), desired_icon_size_); +#if WEBKIT_USING_SKIA + return image.getSkBitmap(); +#elif WEBKIT_USING_CG + return gfx::CGImageToSkBitmap(image.getCGImageRef()); +#endif +} + +} // namespace webkit_glue diff --git a/webkit/glue/image_decoder.h b/webkit/glue/image_decoder.h new file mode 100644 index 0000000..62d9253 --- /dev/null +++ b/webkit/glue/image_decoder.h @@ -0,0 +1,37 @@ +// Copyright (c) 2010 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/basictypes.h" +#include "gfx/size.h" + +class SkBitmap; + +namespace webkit_glue { + +// Provides an interface to WebKit's image decoders. +// +// Note to future: This class should be deleted. We should have our own nice +// image decoders in base/gfx, and our port should use those. Currently, it's +// the other way around. +class ImageDecoder { + public: + // Use the constructor with desired_size when you think you may have an .ico + // format and care about which size you get back. Otherwise, use the 0-arg + // constructor. + ImageDecoder(); + ImageDecoder(const gfx::Size& desired_icon_size); + ~ImageDecoder(); + + // Call this function to decode the image. If successful, the decoded image + // will be returned. Otherwise, an empty bitmap will be returned. + SkBitmap Decode(const unsigned char* data, size_t size) const; + + private: + // Size will be empty to get the largest possible size. + gfx::Size desired_icon_size_; + + DISALLOW_COPY_AND_ASSIGN(ImageDecoder); +}; + +} // namespace webkit_glue diff --git a/webkit/glue/image_resource_fetcher.cc b/webkit/glue/image_resource_fetcher.cc new file mode 100644 index 0000000..6185834 --- /dev/null +++ b/webkit/glue/image_resource_fetcher.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/image_resource_fetcher.h" + +#include "base/callback.h" +#include "gfx/size.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "webkit/glue/image_decoder.h" +#include "third_party/skia/include/core/SkBitmap.h" + +using WebKit::WebFrame; + +namespace webkit_glue { + +ImageResourceFetcher::ImageResourceFetcher( + const GURL& image_url, + WebFrame* frame, + int id, + int image_size, + Callback* callback) + : callback_(callback), + id_(id), + image_url_(image_url), + image_size_(image_size) { + fetcher_.reset(new ResourceFetcher( + image_url, frame, + NewCallback(this, &ImageResourceFetcher::OnURLFetchComplete))); +} + +ImageResourceFetcher::~ImageResourceFetcher() { + if (!fetcher_->completed()) + fetcher_->Cancel(); +} + +void ImageResourceFetcher::OnURLFetchComplete( + const WebKit::WebURLResponse& response, + const std::string& data) { + SkBitmap bitmap; + if (!response.isNull() && response.httpStatusCode() == 200) { + // Request succeeded, try to convert it to an image. + ImageDecoder decoder(gfx::Size(image_size_, image_size_)); + bitmap = decoder.Decode( + reinterpret_cast<const unsigned char*>(data.data()), data.size()); + } // else case: + // If we get here, it means no image from server or couldn't decode the + // response as an image. The delegate will see a null image, indicating + // that an error occurred. + callback_->Run(this, bitmap); + callback_.reset(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/image_resource_fetcher.h b/webkit/glue/image_resource_fetcher.h new file mode 100644 index 0000000..8c6f70c --- /dev/null +++ b/webkit/glue/image_resource_fetcher.h @@ -0,0 +1,63 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ +#define WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "webkit/glue/resource_fetcher.h" + +class SkBitmap; + +namespace webkit_glue { + +// ImageResourceFetcher handles downloading an image for a webview. Once +// downloading is done the supplied callback is notified. ImageResourceFetcher +// is used to download the favicon and images for web apps. +class ImageResourceFetcher { + public: + typedef Callback2<ImageResourceFetcher*, const SkBitmap&>::Type Callback; + + ImageResourceFetcher(const GURL& image_url, + WebKit::WebFrame* frame, + int id, + int image_size, + Callback* callback); + + virtual ~ImageResourceFetcher(); + + // URL of the image we're downloading. + const GURL& image_url() const { return image_url_; } + + // Unique identifier for the request. + int id() const { return id_; } + + private: + // ResourceFetcher::Callback. Decodes the image and invokes callback_. + void OnURLFetchComplete(const WebKit::WebURLResponse& response, + const std::string& data); + + scoped_ptr<Callback> callback_; + + // Unique identifier for the request. + const int id_; + + // URL of the image. + const GURL image_url_; + + // The size of the image. This is only a hint that is used if the image + // contains multiple sizes. A value of 0 results in using the first frame + // of the image. + const int image_size_; + + // Does the actual download. + scoped_ptr<ResourceFetcher> fetcher_; + + DISALLOW_COPY_AND_ASSIGN(ImageResourceFetcher); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_IMAGE_RESOURCE_FETCHER_H_ diff --git a/webkit/glue/inspector_strings.grd b/webkit/glue/inspector_strings.grd new file mode 100644 index 0000000..9d634b7 --- /dev/null +++ b/webkit/glue/inspector_strings.grd @@ -0,0 +1,640 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- This file contains definitions of resources that will be translated for +each locale. Specifically, these are UI strings that are used by WebKit +Inspector that need to be translated for each locale.--> + +<!-- These strings and string descriptions were taken from +WebKit/WebCore/English.lproj/localizedStrings.js @ revision 46732 +so we include the original license below: + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +--> + +<grit base_dir="." latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <output filename="inspectorStrings_am.js" type="js_map_format" lang="am" /> + <output filename="inspectorStrings_ar.js" type="js_map_format" lang="ar" /> + <output filename="inspectorStrings_bg.js" type="js_map_format" lang="bg" /> + <output filename="inspectorStrings_bn.js" type="js_map_format" lang="bn" /> + <output filename="inspectorStrings_ca.js" type="js_map_format" lang="ca" /> + <output filename="inspectorStrings_cs.js" type="js_map_format" lang="cs" /> + <output filename="inspectorStrings_da.js" type="js_map_format" lang="da" /> + <output filename="inspectorStrings_de.js" type="js_map_format" lang="de" /> + <output filename="inspectorStrings_el.js" type="js_map_format" lang="el" /> + <output filename="inspectorStrings_en-GB.js" type="js_map_format" lang="en-GB" /> + <output filename="inspectorStrings_en-US.js" type="js_map_format" lang="en" /> + <output filename="inspectorStrings_es.js" type="js_map_format" lang="es" /> + <output filename="inspectorStrings_es-419.js" type="js_map_format" lang="es-419" /> + <output filename="inspectorStrings_et.js" type="js_map_format" lang="et" /> + <output filename="inspectorStrings_fi.js" type="js_map_format" lang="fi" /> + <output filename="inspectorStrings_fil.js" type="js_map_format" lang="fil" /> + <output filename="inspectorStrings_fr.js" type="js_map_format" lang="fr" /> + <output filename="inspectorStrings_gu.js" type="js_map_format" lang="gu" /> + <output filename="inspectorStrings_he.js" type="js_map_format" lang="he" /> + <output filename="inspectorStrings_hi.js" type="js_map_format" lang="hi" /> + <output filename="inspectorStrings_hr.js" type="js_map_format" lang="hr" /> + <output filename="inspectorStrings_hu.js" type="js_map_format" lang="hu" /> + <output filename="inspectorStrings_id.js" type="js_map_format" lang="id" /> + <output filename="inspectorStrings_it.js" type="js_map_format" lang="it" /> + <output filename="inspectorStrings_ja.js" type="js_map_format" lang="ja" /> + <output filename="inspectorStrings_kn.js" type="js_map_format" lang="kn" /> + <output filename="inspectorStrings_ko.js" type="js_map_format" lang="ko" /> + <output filename="inspectorStrings_lt.js" type="js_map_format" lang="lt" /> + <output filename="inspectorStrings_lv.js" type="js_map_format" lang="lv" /> + <output filename="inspectorStrings_ml.js" type="js_map_format" lang="ml" /> + <output filename="inspectorStrings_mr.js" type="js_map_format" lang="mr" /> + <output filename="inspectorStrings_nl.js" type="js_map_format" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="inspectorStrings_nb.js" type="js_map_format" lang="no" /> + <output filename="inspectorStrings_or.js" type="js_map_format" lang="or" /> + <output filename="inspectorStrings_pl.js" type="js_map_format" lang="pl" /> + <output filename="inspectorStrings_pt-BR.js" type="js_map_format" lang="pt-BR" /> + <output filename="inspectorStrings_pt-PT.js" type="js_map_format" lang="pt-PT" /> + <output filename="inspectorStrings_ro.js" type="js_map_format" lang="ro" /> + <output filename="inspectorStrings_ru.js" type="js_map_format" lang="ru" /> + <output filename="inspectorStrings_sk.js" type="js_map_format" lang="sk" /> + <output filename="inspectorStrings_sl.js" type="js_map_format" lang="sl" /> + <output filename="inspectorStrings_sr.js" type="js_map_format" lang="sr" /> + <output filename="inspectorStrings_sv.js" type="js_map_format" lang="sv" /> + <output filename="inspectorStrings_sw.js" type="js_map_format" lang="sw" /> + <output filename="inspectorStrings_ta.js" type="js_map_format" lang="ta" /> + <output filename="inspectorStrings_te.js" type="js_map_format" lang="te" /> + <output filename="inspectorStrings_th.js" type="js_map_format" lang="th" /> + <output filename="inspectorStrings_tr.js" type="js_map_format" lang="tr" /> + <output filename="inspectorStrings_uk.js" type="js_map_format" lang="uk" /> + <output filename="inspectorStrings_vi.js" type="js_map_format" lang="vi" /> + <output filename="inspectorStrings_zh-CN.js" type="js_map_format" lang="zh-CN" /> + <output filename="inspectorStrings_zh-TW.js" type="js_map_format" lang="zh-TW" /> + </outputs> + <translations> + <!-- TODO add references to each of the XTB files (from the Translation + Console) that contain translations of messages in your project. Each + takes a form like <file path="english.xtb" />. Remember that all file + references are relative to this .grd file. --> + </translations> + <release seq="1"> + <messages fallback_to_english="true"> + <message name="IDS_HEADERS_COUNT" desc="Headers count."> + ''' (<ph name="COUNT">%d<ex>1</ex></ph>) + </message> + <message name="IDS_MESSAGE_REPEAT_COUNT" desc="Message repeat count."> + ''' (repeated <ph name="COUNT">%d<ex>2</ex></ph> times) + </message> + <message name="IDS_SIZE_IN_BYTES" desc="Size in bytes."> + <ph name="SIZE">%.0f<ex>100</ex></ph>B + </message> + <message name="IDS_DURATION_IN_MILLISECONDS" desc="Duration in milliseconds."> + <ph name="DURATION">%.0f<ex>100</ex></ph>ms + </message> + <message name="IDS_DURATION_IN_DAYS" desc="Duration in days."> + <ph name="DURATION">%.1f<ex>5</ex></ph> days + </message> + <message name="IDS_DURATION_IN_HOURS" desc="Duration in hours."> + <ph name="DURATION">%.1f<ex>5</ex></ph>hrs + </message> + <message name="IDS_DURATION_IN_MINUTES" desc="Duration in minutes."> + <ph name="DURATION">%.1f<ex>5</ex></ph>min + </message> + <message name="IDS_SHARE_IN_PERCENTS" desc="Share in percents."> + <ph name="SHARE">%.2f<ex>5</ex></ph>%% + </message> + <message name="IDS_SIZE_IN_KILOBYTES" desc="Size in kilobytes."> + <ph name="SIZE">%.2f<ex>5</ex></ph>KB + </message> + <message name="IDS_DURATION_IN_SECONDS" desc="Duration in seconds."> + <ph name="DURATION">%.2f<ex>5</ex></ph>s + </message> + <message name="IDS_SIZE_IN_MEGABYTES" desc="Size in megabytes."> + <ph name="DURATION">%.3f<ex>5</ex></ph>MB + </message> + <message name="IDS_DURATION_IN_MILLISECONDS_HIGH_PRECISION" desc="Duration in milliseconds, high precision."> + <ph name="DURATION">%.3f<ex>5</ex></ph>ms + </message> + <message name="IDS_ERRORS_COUNT_SINGULAR" desc="Errors count, singular."> + <ph name="COUNT">%d<ex>1</ex></ph> error + </message> + <message name="IDS_ERRORS_AND_WARNINGS_COUNTS_SINGULAR_SINGULAR" desc="Errors and warnings counts, singular-singular."> + <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning + </message> + <message name="IDS_ERRORS_AND_WARNINGS_COUNTS_SINGULAR_PLURAL" desc="Errors and warnings counts, singular-plural."> + <ph name="ERROR_COUNT">%1$d<ex>1</ex></ph> error, <ph name="WARNINGS_COUNT">%2$d<ex>2</ex></ph> warnings + </message> + <message name="IDS_ERRORS_COUNT_PLURAL" desc="Errors count, plural."> + <ph name="COUNT">%d<ex>2</ex></ph> errors + </message> + <message name="IDS_ERRORS_AND_WARNINGS_COUNTS_PLURAL_SINGULAR" desc="Errors and warnings counts, plural-singular."> + <ph name="ERRORS_COUNT">%1$d<ex>2</ex></ph> errors, <ph name="WARNING_COUNT">%2$d<ex>1</ex></ph> warning + </message> + <message name="IDS_ERRORS_AND_WARNINGS_COUNTS_PLURAL_PLURAL" desc="Errors and warnings counts, plural-plural."> + <ph name="ERRORS_COUNT">%1$d<ex>2</ex></ph> errors, <ph name="WARNINGS_COUNT">%2$d<ex>1</ex></ph> warnings + </message> + <message name="IDS_MATCHES_COUNT_PLURAL" desc="Matches count, plural."> + <ph name="COUNT">%d<ex>2</ex></ph> matches + </message> + <message name="IDS_STYLE_CHANGE_SINGULAR" desc="Style changes count, singular."> + <ph name="COUNT">%d<ex>1</ex></ph> style change + </message> + <message name="IDS_STYLE_CHANGE_PLURAL" desc="Style changes count, plural."> + <ph name="COUNT">%d<ex>2</ex></ph> style changes + </message> + <message name="IDS_WARNINGS_COUNT_SINGULAR" desc="Warnings count, singular."> + <ph name="COUNT">%d<ex>1</ex></ph> warning + </message> + <message name="IDS_WARNINGS_COUNT_PLURAL" desc="Warnings count, plural."> + <ph name="COUNT">%d<ex>2</ex></ph> warnings + </message> + <message name="IDS_IMAGE_DIMENSIONS" desc="Image dimensions (uses mutiplication symbol, not x.)"> + <ph name="WIDTH">%1$d<ex>100</ex></ph> × <ph name="HEIGHT">%2$d<ex>100</ex></ph> + </message> + <message name="IDS_INDICATES_THAT_RESOURCE_IS_RETRIEVED_FROM_CACHE" desc="Indicates that resource is retrieved from cache."> + <ph name="RESOURCE_NAME">%s<ex>picture.gif</ex></ph> (from cache) + </message> + <message name="IDS_INDICATES_DURATION_OF_RESOURCE_DOWNLOAD" desc="Indicates duration of resource download."> + <ph name="DURATION">%s<ex>5ms</ex></ph> download + </message> + <message name="IDS_INDICATES_LATENCY_OF_RESOURCE_DOWNLOAD" desc="Indicates latency of resource download."> + <ph name="LATENCY">%s<ex>5ms</ex></ph> latency + </message> + <message name="IDS_INDICATES_DURATION_AND_LATENCY_OF_RESOURCE_DOWNLOAD" desc="Indicates duration and latency of resource download."> + <ph name="LATENCY">%1$s<ex>5ms</ex></ph> latency, <ph name="DURATION">%2$s<ex>5ms</ex></ph> download (<ph name="TOTAL">%3$s<ex>10ms</ex></ph> total) + </message> + <message name="IDS_LABELS_AN_ANONYMOUS_JAVASCRIPT_FUNCTION" desc="Labels an anonymous JavaScript function."> + (anonymous function) + </message> + <message name="IDS_LABELS_PROGRAM_AS_A_WHOLE" desc="Labels program as a whole."> + (program) + </message> + <message name="IDS_LABELS_A_SCRIPT_WITH_URL" desc="Labels a script with URL."> + (program): <ph name="URL">%s<ex>http://site/script.js</ex></ph> + </message> + <message name="IDS_LABELS_A_TEXT_NODE_IN_HTML_TREE" desc="Labels a text node in HTML tree."> + (text) + </message> + <message name="IDS_LABELS_A_WHITESPACE_NODE_IN_HTML_TREE" desc="Labels a whitespace node in HTML tree."> + (whitespace) + </message> + <message name="IDS_MATCHES_COUNT_SINGULAR" desc="Matches count, singular."> + 1 match + </message> + <message name="IDS_SPECIFIES_THAT_SOME_FEATURE_IS_ALWAYS_ENABLED" desc="Specifies that some feature is always enabled."> + Always enable + </message> + <message name="IDS_ERROR_MESSAGE_DISPLAYED_ON_FAILURE_TO_READ_A_DATABASE_TABLE" desc="Error message displayed on failure to read a database table."> + An error occurred trying to\nread the “<ph name="TABLE_NAME">%s<ex>CLIENTS</ex></ph>” table. + </message> + <message name="IDS_ERROR_MESSAGE_DISPLAYED_WHEN_AN_UNEXPECTED_ERROR_OCCURS_DURING_DATABASE_QUERY" desc="Error message displayed when an unexpected error occurs during database query."> + An unexpected error <ph name="ERROR">%s<ex>No memory</ex></ph> occurred. + </message> + <message name="IDS_LABEL_OF_A_GRID_COLUMN_SHOWING_AVERAGE_FUNCTION_EXECUTION_TIME" desc="Label of a grid column showing average function execution time."> + Average + </message> + <message name="IDS_LABEL_OF_A_SIDE_PANEL_SHOWING_BREAKPOINTS" desc="Label of a side panel showing breakpoints."> + Breakpoints + </message> + <message name="IDS_LABEL_OF_A_SIDE_PANEL_SHOWING_CALL_STACK" desc="Label of a side panel showing call stack."> + Call Stack + </message> + <message name="IDS_LABEL_OF_A_GRID_COLUMN_SHOWING_FUNCTION_CALL_COUNT" desc="Label of a grid column showing function call count."> + Calls + </message> + <message name="IDS_HINT_FOR_A_BUTTON_CLEARING_CHANGES_LOG" desc="Hint for a button clearing changes log."> + Clear changes log. + </message> + <message name="IDS_HINT_FOR_A_BUTTON_CLEARING_CONSOLE_LOG" desc="Hint for a button clearing console log."> + Clear console log. + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_CLOSURE_VARIABLES" desc="Label for a section in the scope chain sidebar that shows closure's variables."> + Closure + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_COOKIES_CAPS" desc="Label for a section showing cookies."> + COOKIES + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_COMPUTED_STYLE" desc="Label for a section showing computed style."> + Computed Style + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_COOKIES" desc="Label for a section showing cookies."> + Cookies + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_DATABASES" desc="Label for a section showing databases."> + DATABASES + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_DOM" desc="Label for a section showing DOM."> + DOM + </message> + <message name="IDS_ERROR_MESSAGE_INDICATING_DATABASE_VERSION_MISMATCH" desc="Error message indicating database version mismatch."> + Database no longer has expected version. + </message> + <message name="IDS_LABEL_FOR_THE_TAB_SHOWING_DATABASES" desc="Label for the tab showing databases."> + Databases + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_DEBUGGING_IS_DISABLED" desc="Message indicating that debugging is disabled."> + Debugging disabled. Click to enable. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_DEBUGGING_IS_ENABLED" desc="Message indicating that debugging is enabled."> + Debugging enabled. Click to disable. + </message> + <message name="IDS_A_HINT_THAT_REMINDS_TO_TURN_ON_DEBUGGER" desc="A hint that reminds to turn on debugger."> + Debugging scripts requires you to start the debugger. + </message> + <message name="IDS_A_BUTTON_FOR_DELETING_DOM_STORAGE_ITEMS" desc="A button for deleting DOM storage items."> + Delete + </message> + <message name="IDS_LABEL_OF_IMAGE_DIMENSIONS_GRID_COLUMN" desc="Label of image dimensions grid column."> + Dimensions + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_DOCKS_INSPECTOR_TO_MAIN_WINDOW" desc="Hint for a button that docks Inspector to main window."> + Dock to main window. + </message> + <message name="IDS_LABEL_FOR_DOCUMENTS_RESOURCE_SECTION" desc="Label for documents resource section."> + Documents + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_DISABLES_PAUSE_EXECUTION_ON_EXCEPTIONS" desc="Hint for a button that disables pause execution on exceptions."> + Don't pause on exceptions. + </message> + <message name="IDS_DOUBLE_CLICK_TO_ADD" desc="Instruction on adding an element."> + Double-Click to Add + </message> + <message name="IDS_LABEL_FOR_HTML_ELEMENTS_PANEL" desc="Label for HTML elements panel."> + Elements + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_ENABLES_DEBUGGING" desc="Hint for a button that enables debugging."> + Enable Debugging + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_ENABLES_PROFILING" desc="Hint for a button that enables profiling."> + Enable Profiling + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_ENABLES_RESOURCE_TRACKING" desc="Hint for a button that enables resource tracking."> + Enable resource tracking + </message> + <message name="IDS_A_WARNING_MESSAGE_ABOUT_DEBUGGING" desc="A warning message about debugging."> + Enabling debugging will make scripts run slower. + </message> + <message name="IDS_A_WARNING_MESSAGE_ABOUT_PROFILING" desc="A warning message about profiling."> + Enabling profiling will make scripts run slower. + </message> + <message name="IDS_A_WARNING_MESSAGE_ABOUT_RESOURCE_TRACKING" desc="A warning message about resource tracking."> + Enabling resource tracking will reload the page and make page loading slower. + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_EVENT_DOCUMENT_VARIABLES" desc="Label for a section in the scope chain sidebar that shows event document's variables."> + Event Document + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_EVENT_TARGET_VARIABLES" desc="Label for a section in the scope chain sidebar that shows event target's variables."> + Event Target + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_EXCLUDES_A_FUNCTION_FROM_EXECUTION_PROFILE" desc="Label for a button that excludes a function from execution profile."> + Exclude selected function. + </message> + <message name="IDS_LABEL_OF_IMAGE_FILE_SIZE_GRID_COLUMN" desc="Label of image file size grid column."> + File size + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_FOCUSES_ON_A_FUNCTION_FROM_EXECUTION_PROFILE" desc="Label for a button that focuses on a function from execution profile."> + Focus selected function. + </message> + <message name="IDS_LABEL_FOR_FONTS_RESOURCE_SECTION" desc="Label for fonts resource section."> + Fonts + </message> + <message name="IDS_LABEL_OF_A_GRID_COLUMN_SHOWING_FUNCTION_NAME" desc="Label of a grid column showing function name."> + Function + </message> + <message name="IDS_LABEL_FOR_GRAPHS_SIDEBAR" desc="Label for graphs sidebar."> + GRAPHS + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_GLOBAL_VARIABLES" desc="Label for a section in the scope chain sidebar that shows global variables."> + Global + </message> + <message name="IDS_LABEL_INDICATING_THAT_BOTTOM-UP_HEAVY_PROFILE_IS_SHOWN" desc="Label indicating that bottom-up (heavy) profile is shown."> + Heavy (Bottom Up) + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_HIDES_CHANGES" desc="Label for a button that hides changes view."> + Hide changes view. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_HIDES_CONSOLE" desc="Label for a button that hides console."> + Hide console. + </message> + <message name="IDS_LABEL_FOR_IMAGES_RESOURCE_SECTION" desc="Label for images resource section."> + Images + </message> + <message name="IDS_LABEL_FOR_INLINE_STYLE_ATTRIBUTE" desc="Label for inline style attribute."> + Inline Style Attribute + </message> + <message name="IDS_LABEL_FOR_A_GRID_COLUMN_SHOWING_PROPERTY_KEY" desc="Label for a grid column showing property key."> + Key + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_LOCAL_STORAGE" desc="Label for a section showing local storage."> + LOCAL STORAGE + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_LOCAL_VARIABLES" desc="Label for a section in the scope chain sidebar that shows local variables."> + Local + </message> + <message name="IDS_LABEL_OF_IMAGE_MIME_TYPE_GRID_COLUMN" desc="Label of image MIME type grid column."> + MIME type + </message> + <message name="IDS_LABEL_FOR_HTML_ELEMENT_METRICS_SIDE_PANEL" desc="Label for HTML element metrics side panel."> + Metrics + </message> + <message name="IDS_MESSAGE_IN_BREAKPOINTS_SIDEBAR_PANEL_INDICATING_THAT_THERE_ARE_NO_BREAKPOINTS" desc="Message in breakpoints sidebar panel indicating that there are no breakpoints."> + No Breakpoints + </message> + <message name="IDS_MESSAGE_IN_OBJECT_PROPERTIES_SIDEBAR_PANEL_INDICATING_THAT_THERE_ARE_NO_PROPERTIES" desc="Message in object properties sidebar panel indicating that there are no properties."> + No Properties + </message> + <message name="IDS_MESSAGE_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_SHOWING_VARIABLES_ABSENCE" desc="Message for a section in the scope chain sidebar showing variables absence."> + No Variables + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_SEARCHED_STRING_DOESN'T_FOUND" desc="Message indicating that searched string doesn't found."> + Not Found + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_PROGRAM_ISN'T_PAUSED" desc="Message indicating that program isn't paused."> + Not Paused + </message> + <message name="IDS_SPECIFIES_THAT_SOME_FEATURE_IS_ONLY_ENABLED_FOR_CURRENT_SESSION" desc="Specifies that some feature is only enabled for current session."> + Only enable for this session + </message> + <message name="IDS_LABEL_FOR_OTHER_RESOURCES_SECTION" desc="Label for other resources section."> + Other + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_ENABLES_PAUSE_EXECUTION_ON_EXCEPTIONS" desc="Hint for a button that enables pause execution on exceptions."> + Pause on exceptions. + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_PAUSES_SCRIPT_EXECUTION" desc="Hint for a button that pauses script execution."> + Pause script execution. + </message> + <message name="IDS_HINT_FOR_A_PAUSE_BUTTON_IN_DEBUGGER_INDICATING_THAT_EXECUTION_IS_PAUSED" desc="Hint for a pause button in debugger, indicating that execution is paused."> + Paused + </message> + <message name="IDS_HINT_FOR_A_PAUSE_BUTTON_IN_DEBUGGER_INDICATING_THAT_EXECUTION_IS_BEING_PAUSED" desc="Hint for a pause button in debugger, indicating that execution is being paused."> + Pausing + </message> + <message name="IDS_LABEL_FOR_AN_EXECUTION_PROFILE" desc="Label for an execution profile."> + Profile <ph name="ID">%d<ex>1</ex></ph> + </message> + <message name="IDS_LABEL_FOR_PROFILES_TAB" desc="Label for profiles tab."> + Profiles + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_PROFILING_IS_DISABLED" desc="Message indicating that profiling is disabled."> + Profiling disabled. Click to enable. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_PROFILING_IS_ENABLED" desc="Message indicating that profiling is enabled."> + Profiling enabled. Click to disable. + </message> + <message name="IDS_LABEL_FOR_OBJECT_PROPERTIES_SIDEBAR" desc="Label for object properties sidebar."> + Properties + </message> + <message name="IDS_LABEL_FOR_THE_PROTOTYPE_PROPERTY" desc="Label for the prototype property."> + Prototype + </message> + <message name="IDS_LABEL_FOR_DATABASE_QUERY" desc="Label for database query."> + Query + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_RESOURCES" desc="Label for a section showing resources."> + RESOURCES + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_REFRESHES_CURRENT_VIEW" desc="Label for a button that refreshes current view."> + Refresh + </message> + <message name="IDS_LABEL_FOR_HTTP_REQUEST_HEADERS" desc="Label for HTTP request headers."> + Request Headers + </message> + <message name="IDS_A_WARNING_ABOUT_RESOURCE_TYPE_MISMATCH" desc="A warning about resource type mismatch."> + Resource interpreted as <ph name="REAL_TYPE">%1$s<ex>text/javascript</ex></ph> but transferred with MIME type <ph name="SPECIFIED_TYPE">%2$s<ex>text/javascript</ex></ph>. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_RESOURCE_TRACKING_IS_DISABLED" desc="Message indicating that resource tracking is disabled."> + Resource tracking disabled. Click to enable. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_RESOURCE_TRACKING_IS_ENABLED" desc="Message indicating that resource tracking is enabled."> + Resource tracking enabled. Click to disable. + </message> + <message name="IDS_LABEL_FOR_RESOURCES_TAB" desc="Label for resources tab."> + Resources + </message> + <message name="IDS_LABEL_FOR_HTTP_REQUEST_RESPONSE_HEADERS" desc="Label for HTTP request response headers."> + Response Headers + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_SHOWS_ALL_FUNCTIONS_IN_PROFILER" desc="Label for a button that shows all functions in profiler."> + Restore all functions. + </message> + <message name="IDS_LABEL_FOR_NTH_PROFILER_RUN" desc="Label for Nth profiler run."> + Run <ph name="ID">%d<ex>1</ex></ph> + </message> + <message name="IDS_LABEL_FOR_A_SECTION_SHOWING_SESSION_STORAGE" desc="Label for a section showing session storage."> + SESSION STORAGE + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_SCOPE_VARIABLES" desc="Label for a section in the scope chain sidebar that shows scope variables."> + Scope Variables + </message> + <message name="IDS_LABEL_FOR_SCRIPTS_RESOURCE_SECTION" desc="Label for scripts resource section."> + Scripts + </message> + <message name="IDS_LABEL_INDICATING_CURRENT_SEARCH" desc="Label indicating current search."> + Search <ph name="STRING">%s<ex>foo</ex></ph> + </message> + <message name="IDS_HINT_THAT_ELEMENT_SHOULD_BE_SELECTED" desc="Hint that element should be selected."> + Select an element in the page to inspect it. + </message> + <message name="IDS_LABEL_OF_A_GRID_COLUMN_SHOWING_FUNCTION_SELF_EXECUTION_TIME" desc="Label of a grid column showing function self execution time."> + Self + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_TOGGLING_SHOWING_OF_ABSOLUTE_TIMES" desc="Label for a button toggling showing of absolute times."> + Show absolute total and self times. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_SHOWS_CHANGES" desc="Label for a button that shows changes view."> + Show changes view. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_SHOWS_CONSOLE" desc="Label for a button that shows console."> + Show console. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_ENABLING_SHOWING_OF_INHERITED_STYLES" desc="Label for a button enabling showing of inherited styles."> + Show inherited + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_SHOWS_THE_NEXT_SCRIPT_RESOURCE" desc="Label for a button that shows the next script resource."> + Show the next script resource. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_SHOWS_THE_PREVIOUS_SCRIPT_RESOURCE" desc="Label for a button that shows the previous script resource."> + Show the previous script resource. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_TOGGLING_SHOWING_TIMES_AS_PERCENTAGES" desc="Label for a button toggling showing times as percentages."> + Show total and self times as percentages. + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_SIZE_GRAPH_1" desc="Label for a resource size graph."> + Size + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_2" desc="Sort option for resources graph."> + Sort by Duration + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_3" desc="Sort option for resources graph."> + Sort by End Time + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_4" desc="Sort option for resources graph."> + Sort by Latency + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_5" desc="Sort option for resources graph."> + Sort by Response Time + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_6" desc="Sort option for resources graph."> + Sort by Size + </message> + <message name="IDS_SORT_OPTION_FOR_RESOURCES_GRAPH_7" desc="Sort option for resources graph."> + Sort by Start Time + </message> + <message name="IDS_LABEL_FOR_SCRIPT_SOURCE" desc="Label for script source."> + Source + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_STARTS_PROFILING" desc="Label for a button that starts profiling."> + Start profiling. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_STEPS_INTO_NEXT_FUNCTION_CALL" desc="Label for a button that steps into next function call."> + Step into next function call. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_STEPS_OUT_OF_CURRENT_FUNCTION" desc="Label for a button that steps out of current function."> + Step out of current function. + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_STEPS_OVER_NEXT_FUNCTION_CALL" desc="Label for a button that steps over next function call."> + Step over next function call. + </message> + <message name="IDS_HINT_FOR_A_PAUSE_BUTTON_IN_DEBUGGER,_INDICATING_THAT_DEBUGGER_IS_STEPPING_INTO_NEXT_STATEMENT" desc="Hint for a pause button in debugger, indicating that debugger is stepping into next statement."> + Stepping + </message> + <message name="IDS_LABEL_FOR_A_BUTTON_THAT_STOPS_PROFILING" desc="Label for a button that stops profiling."> + Stop profiling. + </message> + <message name="IDS_LABEL_FOR_STYLE_ATTRIBUTE" desc="Label for style attribute."> + Style Attribute + </message> + <message name="IDS_LABEL_FOR_CSS_STYLES_SIDE_PANEL" desc="Label for CSS styles side panel."> + Styles + </message> + <message name="IDS_LABEL_FOR_STYLESHEETS_RESOURCE_SECTION" desc="Label for stylesheets resource section."> + Stylesheets + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_DATABASE_TABLE_IS_EMPTY" desc="Message indicating that database table is empty."> + The “<ph name="TABLE_NAME">%s<ex>foo</ex></ph>”\ntable is empty. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_CURRENT_SITE_DOESN'T_HAVE_COOKIES" desc="Message indicating that current site doesn't have cookies."> + This site has no cookies. + </message> + <message name="IDS_MESSAGE_INDICATING_THAT_STORAGE_IS_EMPTY" desc="Message indicating that storage is empty."> + This storage is empty. + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TIME_GRAPH" desc="Label for a resource time graph."> + Time + </message> + <message name="IDS_LABEL_OF_A_GRID_COLUMN_SHOWING_FUNCTION_TOTAL_EXECUTION_TIME" desc="Label of a grid column showing function total execution time."> + Total + </message> + <message name="IDS_LABEL_INDICATING_THAT_TOP-DOWN_(TREE)_PROFILE_IS_SHOWN" desc="Label indicating that top-down (tree) profile is shown."> + Tree (Top Down) + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_UNDOCKS_INSPECTOR_FROM_MAIN_WINDOW" desc="Hint for a button that undocks Inspector from main window."> + Undock into separate window. + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_TOGGLES_SHOWING_LARGE_RESOURCE_ROWS" desc="Hint for a button that toggles showing large resource rows."> + Use large resource rows. + </message> + <message name="IDS_HINT_FOR_A_BUTTON_THAT_TOGGLES_SHOWING_SMALL_RESOURCE_ROWS" desc="Hint for a button that toggles showing small resource rows."> + Use small resource rows. + </message> + <message name="IDS_LABEL_FOR_A_GRID_COLUMN_SHOWING_PROPERTY_VALUE" desc="Label for a grid column showing property value."> + Value + </message> + <message name="IDS_LABEL_FOR_A_SECTION_IN_THE_SCOPE_CHAIN_SIDEBAR_THAT_SHOWS_WITH_BLOCK_VARIABLES" desc="Label for a section in the scope chain sidebar that shows with block variables."> + With Block + </message> + <message name="IDS_LABEL_FOR_XHR_RESOURCE_SECTION" desc="Label for XHR resource section."> + XHR + </message> + <message name="IDS_HINT_MESSAGE_ABOUT_POTENTIAL_RESOURCES_COMPRESSION" desc="Hint message about potential resources compression."> + You could save bandwidth by having your web server compress this transfer with gzip or zlib. + </message> + <message name="IDS_HINT_MESSAGE_ABOUT_NEED_FOR_ENABLING_DEBUGGING" desc="Hint message about need for enabling debugging."> + You need to enable debugging before you can use the Scripts panel. + </message> + <message name="IDS_HINT_MESSAGE_ABOUT_NEED_FOR_ENABLING_PROFILING" desc="Hint message about need for enabling profiling."> + You need to enable profiling before you can use the Profiles panel. + </message> + <message name="IDS_HINT_MESSAGE_ABOUT_NEED_FOR_ENABLING_RESOURCE_TRACKING" desc="Hint message about need for enabling resource tracking."> + You need to enable resource tracking to use this panel. + </message> + <message name="IDS_LABEL_FOR_A_BOX_SHOWING_HTML_ELEMENT_BORDER_SIZE" desc="Label for a box showing HTML element border size."> + border + </message> + <message name="IDS_LABEL_FOR_A_BOX_SHOWING_HTML_ELEMENT_CONTENT_SIZE" desc="Label for a box showing HTML element content size."> + content + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE" desc="Label for a resource type."> + document + </message> + <message name="IDS_LABEL_FOR_HTML_ELEMENT'S_ATTRIBUTE" desc="Label for HTML element's attribute."> + element’s “<ph name="NAME">%s<ex>width</ex></ph>” attribute + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_1" desc="Label for a resource type."> + font + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_2" desc="Label for a resource type."> + image + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_3" desc="Label for a resource type."> + inline stylesheet + </message> + <message name="IDS_LABEL_FOR_LINE_POSITION" desc="Label for line position."> + line <ph name="LINE_NUMBER">%d<ex>25</ex></ph> + </message> + <message name="IDS_LABEL_FOR_A_BOX_SHOWING_HTML_ELEMENT_MARGIN_SIZE" desc="Label for a box showing HTML element margin size."> + margin + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_4" desc="Label for a resource type."> + other + </message> + <message name="IDS_LABEL_FOR_A_BOX_SHOWING_HTML_ELEMENT_PADDING_SIZE" desc="Label for a box showing HTML element padding size."> + padding + </message> + <message name="IDS_LABEL_FOR_A_POSITION" desc="Label for a position."> + position + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_5" desc="Label for a resource type."> + script + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_6" desc="Label for a resource type."> + stylesheet + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_7" desc="Label for a resource type."> + user agent stylesheet + </message> + <message name="IDS_LABEL_FOR_A_RESOURCE_TYPE_8" desc="Label for a resource type."> + user stylesheet + </message> + <message name="IDS_LABEL_VIA_INSPECTOR" desc="Label for a style attribute added via Inspector."> + via inspector + </message> + </messages> + </release> +</grit> diff --git a/webkit/glue/media/buffered_data_source.cc b/webkit/glue/media/buffered_data_source.cc new file mode 100644 index 0000000..dfac588 --- /dev/null +++ b/webkit/glue/media/buffered_data_source.cc @@ -0,0 +1,1052 @@ +// Copyright (c) 2010 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/callback.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "media/base/filter_host.h" +#include "media/base/media_format.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/glue/media/buffered_data_source.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; +const int64 kPositionNotSpecified = -1; +const int kHttpOK = 200; +const int kHttpPartialContent = 206; + +// Define the number of bytes in a megabyte. +const size_t kMegabyte = 1024 * 1024; + +// Backward capacity of the buffer, by default 2MB. +const size_t kBackwardCapcity = 2 * kMegabyte; + +// Forward capacity of the buffer, by default 10MB. +const size_t kForwardCapacity = 10 * kMegabyte; + +// The threshold of bytes that we should wait until the data arrives in the +// future instead of restarting a new connection. This number is defined in the +// number of bytes, we should determine this value from typical connection speed +// and amount of time for a suitable wait. Now I just make a guess for this +// number to be 2MB. +// TODO(hclam): determine a better value for this. +const int kForwardWaitThreshold = 2 * kMegabyte; + +// Defines how long we should wait for more data before we declare a connection +// timeout and start a new request. +// TODO(hclam): Set it to 5s, calibrate this value later. +const int kTimeoutMilliseconds = 5000; + +// Defines how many times we should try to read from a buffered resource loader +// before we declare a read error. After each failure of read from a buffered +// resource loader, a new one is created to be read. +const int kReadTrials = 3; + +// BufferedDataSource has an intermediate buffer, this value governs the initial +// size of that buffer. It is set to 32KB because this is a typical read size +// of FFmpeg. +const int kInitialReadBufferSize = 32768; + +// Returns true if |url| operates on HTTP protocol. +bool IsHttpProtocol(const GURL& url) { + return url.SchemeIs(kHttpScheme) || url.SchemeIs(kHttpsScheme); +} + +bool IsDataProtocol(const GURL& url) { + return url.SchemeIs(kDataScheme); +} + +} // namespace + +namespace webkit_glue { +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader +BufferedResourceLoader::BufferedResourceLoader( + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, + const GURL& url, + int64 first_byte_position, + int64 last_byte_position) + : buffer_(new media::SeekableBuffer(kBackwardCapcity, kForwardCapacity)), + deferred_(false), + defer_allowed_(true), + completed_(false), + range_requested_(false), + partial_response_(false), + bridge_factory_(bridge_factory), + url_(url), + first_byte_position_(first_byte_position), + last_byte_position_(last_byte_position), + start_callback_(NULL), + bridge_(NULL), + offset_(0), + content_length_(kPositionNotSpecified), + instance_size_(kPositionNotSpecified), + read_callback_(NULL), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + first_offset_(0), + last_offset_(0) { +} + +BufferedResourceLoader::~BufferedResourceLoader() { +} + +void BufferedResourceLoader::Start(net::CompletionCallback* start_callback, + NetworkEventCallback* event_callback) { + // Make sure we have not started. + DCHECK(!bridge_.get()); + DCHECK(!start_callback_.get()); + DCHECK(!event_callback_.get()); + DCHECK(start_callback); + DCHECK(event_callback); + + start_callback_.reset(start_callback); + event_callback_.reset(event_callback); + + if (first_byte_position_ != kPositionNotSpecified) { + range_requested_ = true; + // TODO(hclam): server may not support range request so |offset_| may not + // equal to |first_byte_position_|. + offset_ = first_byte_position_; + } + + // Creates the bridge on render thread since we can only access + // ResourceDispatcher on this thread. + bridge_.reset( + bridge_factory_->CreateBridge( + url_, + IsMediaCacheEnabled() ? net::LOAD_NORMAL : net::LOAD_BYPASS_CACHE, + first_byte_position_, + last_byte_position_)); + + // Increment the reference count right before we start the request. This + // reference will be release when this request has ended. + AddRef(); + + // And start the resource loading. + bridge_->Start(this); +} + +void BufferedResourceLoader::Stop() { + // Reset callbacks. + start_callback_.reset(); + event_callback_.reset(); + read_callback_.reset(); + + // Use the internal buffer to signal that we have been stopped. + // TODO(hclam): Not so pretty to do this. + if (!buffer_.get()) + return; + + // Destroy internal buffer. + buffer_.reset(); + + if (bridge_.get()) { + // Cancel the request. This method call will cancel the request + // asynchronously. We may still get data or messages until we receive + // a response completed message. + if (deferred_) + bridge_->SetDefersLoading(false); + deferred_ = false; + bridge_->Cancel(); + } +} + +void BufferedResourceLoader::Read(int64 position, + int read_size, + uint8* buffer, + net::CompletionCallback* read_callback) { + DCHECK(!read_callback_.get()); + DCHECK(buffer_.get()); + DCHECK(read_callback); + DCHECK(buffer); + + // Save the parameter of reading. + read_callback_.reset(read_callback); + read_position_ = position; + read_size_ = read_size; + read_buffer_ = buffer; + + // If read position is beyond the instance size, we cannot read there. + if (instance_size_ != kPositionNotSpecified && + instance_size_ <= read_position_) { + DoneRead(0); + return; + } + + // Make sure |offset_| and |read_position_| does not differ by a large + // amount. + if (read_position_ > offset_ + kint32max || + read_position_ < offset_ + kint32min) { + DoneRead(net::ERR_CACHE_MISS); + return; + } + + // Prepare the parameters. + first_offset_ = static_cast<int>(read_position_ - offset_); + last_offset_ = first_offset_ + read_size_; + + // If we can serve the request now, do the actual read. + if (CanFulfillRead()) { + ReadInternal(); + DisableDeferIfNeeded(); + return; + } + + // If we expected the read request to be fulfilled later, returns + // immediately and let more data to flow in. + if (WillFulfillRead()) + return; + + // Make a callback to report failure. + DoneRead(net::ERR_CACHE_MISS); +} + +int64 BufferedResourceLoader::GetBufferedFirstBytePosition() { + if (buffer_.get()) + return offset_ - static_cast<int>(buffer_->backward_bytes()); + return kPositionNotSpecified; +} + +int64 BufferedResourceLoader::GetBufferedLastBytePosition() { + if (buffer_.get()) + return offset_ + static_cast<int>(buffer_->forward_bytes()) - 1; + return kPositionNotSpecified; +} + +void BufferedResourceLoader::SetAllowDefer(bool is_allowed) { + defer_allowed_ = is_allowed; + DisableDeferIfNeeded(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader, +// webkit_glue::ResourceLoaderBridge::Peer implementations +bool BufferedResourceLoader::OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + DCHECK(bridge_.get()); + + // Save the new URL. + url_ = new_url; + // TODO(wtc): should we return a new first party for cookies URL? + *has_new_first_party_for_cookies = false; + + // The load may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) + return true; + + if (!IsProtocolSupportedForMedia(new_url)) { + DoneStart(net::ERR_ADDRESS_INVALID); + Stop(); + return false; + } + return true; +} + +void BufferedResourceLoader::OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + DCHECK(bridge_.get()); + + // The loader may have been stopped and |start_callback| is destroyed. + // In this case we shouldn't do anything. + if (!start_callback_.get()) + return; + + // We make a strong assumption that when we reach here we have either + // received a response from HTTP/HTTPS protocol or the request was + // successful (in particular range request). So we only verify the partial + // response for HTTP and HTTPS protocol. + if (IsHttpProtocol(url_)) { + int error = net::OK; + if (!info.headers) { + // We expect to receive headers because this is a HTTP or HTTPS protocol, + // if not report failure. + error = net::ERR_INVALID_RESPONSE; + } else { + if (info.headers->response_code() == kHttpPartialContent) + partial_response_ = true; + + if (range_requested_ && partial_response_) { + // If we have verified the partial response and it is correct, we will + // return net::OK. + if (!VerifyPartialResponse(info)) + error = net::ERR_INVALID_RESPONSE; + } else if (info.headers->response_code() != kHttpOK) { + // We didn't request a range but server didn't reply with "200 OK". + error = net::ERR_FAILED; + } + } + + if (error != net::OK) { + DoneStart(error); + Stop(); + return; + } + } else { + // For any protocol other than HTTP and HTTPS, assume range request is + // always fulfilled. + partial_response_ = range_requested_; + } + + // |info.content_length| can be -1, in that case |content_length_| is + // not specified and this is a streaming response. + content_length_ = info.content_length; + + // If we have not requested a range, then the size of the instance is equal + // to the content length. + if (!partial_response_) + instance_size_ = content_length_; + + // Calls with a successful response. + DoneStart(net::OK); +} + +void BufferedResourceLoader::OnReceivedData(const char* data, int len) { + DCHECK(bridge_.get()); + + // If this loader has been stopped, |buffer_| would be destroyed. + // In this case we shouldn't do anything. + if (!buffer_.get()) + return; + + // Writes more data to |buffer_|. + buffer_->Append(reinterpret_cast<const uint8*>(data), len); + + // If there is an active read request, try to fulfill the request. + if (HasPendingRead() && CanFulfillRead()) { + ReadInternal(); + } else if (!defer_allowed_) { + // If we're not allowed to defer, slide the buffer window forward instead + // of deferring. + if (buffer_->forward_bytes() > buffer_->forward_capacity()) { + size_t excess = buffer_->forward_bytes() - buffer_->forward_capacity(); + bool success = buffer_->Seek(excess); + DCHECK(success); + offset_ += first_offset_ + excess; + } + } + + // At last see if the buffer is full and we need to defer the downloading. + EnableDeferIfNeeded(); + + // Notify that we have received some data. + NotifyNetworkEvent(); +} + +void BufferedResourceLoader::OnCompletedRequest( + const URLRequestStatus& status, const std::string& security_info) { + DCHECK(bridge_.get()); + + // Saves the information that the request has completed. + completed_ = true; + + // If there is a start callback, calls it. + if (start_callback_.get()) { + DoneStart(status.os_error()); + } + + // If there is a pending read but the request has ended, returns with what + // we have. + if (HasPendingRead()) { + // Make sure we have a valid buffer before we satisfy a read request. + DCHECK(buffer_.get()); + + if (status.is_success()) { + // Try to fulfill with what is in the buffer. + if (CanFulfillRead()) + ReadInternal(); + else + DoneRead(net::ERR_CACHE_MISS); + } else { + // If the request has failed, then fail the read. + DoneRead(net::ERR_FAILED); + } + } + + // There must not be any outstanding read request. + DCHECK(!HasPendingRead()); + + // Notify that network response is completed. + NotifyNetworkEvent(); + + // We incremented the reference count when the loader was started. We balance + // that reference here so that we get destroyed. This is also the only safe + // place to destroy the ResourceLoaderBridge. + bridge_.reset(); + Release(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader, private +void BufferedResourceLoader::EnableDeferIfNeeded() { + if (!defer_allowed_) + return; + + if (!deferred_ && + buffer_->forward_bytes() >= buffer_->forward_capacity()) { + deferred_ = true; + + if (bridge_.get()) + bridge_->SetDefersLoading(true); + + NotifyNetworkEvent(); + } +} + +void BufferedResourceLoader::DisableDeferIfNeeded() { + if (deferred_ && + (!defer_allowed_ || + buffer_->forward_bytes() < buffer_->forward_capacity() / 2)) { + deferred_ = false; + + if (bridge_.get()) + bridge_->SetDefersLoading(false); + + NotifyNetworkEvent(); + } +} + +bool BufferedResourceLoader::CanFulfillRead() { + // If we are reading too far in the backward direction. + if (first_offset_ < 0 && + first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) + return false; + + // If the start offset is too far ahead. + if (first_offset_ >= static_cast<int>(buffer_->forward_bytes())) + return false; + + // At the point, we verified that first byte requested is within the buffer. + // If the request has completed, then just returns with what we have now. + if (completed_) + return true; + + // If the resource request is still active, make sure the whole requested + // range is covered. + if (last_offset_ > static_cast<int>(buffer_->forward_bytes())) + return false; + + return true; +} + +bool BufferedResourceLoader::WillFulfillRead() { + // Reading too far in the backward direction. + if (first_offset_ < 0 && + first_offset_ + static_cast<int>(buffer_->backward_bytes()) < 0) + return false; + + // Try to read too far ahead. + if (last_offset_ > kForwardWaitThreshold) + return false; + + // The resource request has completed, there's no way we can fulfill the + // read request. + if (completed_) + return false; + + return true; +} + +void BufferedResourceLoader::ReadInternal() { + // Seek to the first byte requested. + bool ret = buffer_->Seek(first_offset_); + DCHECK(ret); + + // Then do the read. + int read = static_cast<int>(buffer_->Read(read_buffer_, read_size_)); + offset_ += first_offset_ + read; + + // And report with what we have read. + DoneRead(read); +} + +bool BufferedResourceLoader::VerifyPartialResponse( + const ResourceLoaderBridge::ResponseInfo& info) { + int64 first_byte_position, last_byte_position, instance_size; + if (!info.headers->GetContentRange(&first_byte_position, + &last_byte_position, + &instance_size)) { + return false; + } + + if (instance_size != kPositionNotSpecified) + instance_size_ = instance_size; + + if (first_byte_position_ != -1 && + first_byte_position_ != first_byte_position) { + return false; + } + + // TODO(hclam): I should also check |last_byte_position|, but since + // we will never make such a request that it is ok to leave it unimplemented. + return true; +} + +void BufferedResourceLoader::DoneRead(int error) { + read_callback_->RunWithParams(Tuple1<int>(error)); + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = NULL; + first_offset_ = 0; + last_offset_ = 0; +} + +void BufferedResourceLoader::DoneStart(int error) { + start_callback_->RunWithParams(Tuple1<int>(error)); + start_callback_.reset(); +} + +void BufferedResourceLoader::NotifyNetworkEvent() { + if (event_callback_.get()) + event_callback_->Run(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, static methods +bool BufferedDataSource::IsMediaFormatSupported( + const media::MediaFormat& media_format) { + std::string mime_type; + std::string url; + if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) && + media_format.GetAsString(media::MediaFormat::kURL, &url)) { + GURL gurl(url); + + // This data source doesn't support data:// protocol, so reject it + // explicitly. + if (IsProtocolSupportedForMedia(gurl) && !IsDataProtocol(gurl)) + return true; + } + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, protected +BufferedDataSource::BufferedDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + : total_bytes_(kPositionNotSpecified), + loaded_(false), + streaming_(false), + bridge_factory_(bridge_factory), + loader_(NULL), + network_activity_(false), + initialize_callback_(NULL), + read_callback_(NULL), + read_position_(0), + read_size_(0), + read_buffer_(NULL), + read_attempts_(0), + intermediate_read_buffer_(new uint8[kInitialReadBufferSize]), + intermediate_read_buffer_size_(kInitialReadBufferSize), + render_loop_(render_loop), + stop_signal_received_(false), + stopped_on_render_loop_(false), + media_is_paused_(true) { +} + +BufferedDataSource::~BufferedDataSource() { +} + +// A factory method to create BufferedResourceLoader using the read parameters. +// This method can be overrided to inject mock BufferedResourceLoader object +// for testing purpose. +BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position) { + DCHECK(MessageLoop::current() == render_loop_); + + return new BufferedResourceLoader(bridge_factory_.get(), url_, + first_byte_position, + last_byte_position); +} + +// This method simply returns kTimeoutMilliseconds. The purpose of this +// method is to be overidded so as to provide a different timeout value +// for testing purpose. +base::TimeDelta BufferedDataSource::GetTimeoutMilliseconds() { + return base::TimeDelta::FromMilliseconds(kTimeoutMilliseconds); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, media::MediaFilter implementation +void BufferedDataSource::Initialize(const std::string& url, + media::FilterCallback* callback) { + // Saves the url. + url_ = GURL(url); + + if (!IsProtocolSupportedForMedia(url_)) { + // This method is called on the thread where host() lives so it is safe + // to make this call. + host()->SetError(media::PIPELINE_ERROR_NETWORK); + callback->Run(); + delete callback; + return; + } + + DCHECK(callback); + initialize_callback_.reset(callback); + + media_format_.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kApplicationOctetStream); + media_format_.SetAsString(media::MediaFormat::kURL, url); + + // Post a task to complete the initialization task. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::InitializeTask)); +} + +void BufferedDataSource::Stop(media::FilterCallback* callback) { + { + AutoLock auto_lock(lock_); + stop_signal_received_ = true; + } + if (callback) { + callback->Run(); + delete callback; + } + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::CleanupTask)); +} + +void BufferedDataSource::SetPlaybackRate(float playback_rate) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::SetPlaybackRateTask, + playback_rate)); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, media::DataSource implementation +void BufferedDataSource::Read(int64 position, size_t size, uint8* data, + media::DataSource::ReadCallback* read_callback) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::ReadTask, + position, static_cast<int>(size), data, read_callback)); +} + +bool BufferedDataSource::GetSize(int64* size_out) { + if (total_bytes_ != kPositionNotSpecified) { + *size_out = total_bytes_; + return true; + } + *size_out = 0; + return false; +} + +bool BufferedDataSource::IsStreaming() { + return streaming_; +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, render thread tasks +void BufferedDataSource::InitializeTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!loader_.get()); + DCHECK(!stopped_on_render_loop_); + + // Kick starts the watch dog task that will handle connection timeout. + // We run the watch dog 2 times faster the actual timeout so as to catch + // the timeout more accurately. + watch_dog_timer_.Start( + GetTimeoutMilliseconds() / 2, + this, + &BufferedDataSource::WatchDogTask); + + if (IsHttpProtocol(url_)) { + // Fetch only first 1024 bytes as this usually covers the header portion + // of a media file that gives enough information about the codecs, etc. + // This also serve as a probe to determine server capability to serve + // range request. + // TODO(hclam): Do some experiments for the best approach. + loader_ = CreateResourceLoader(0, 1024); + loader_->Start( + NewCallback(this, &BufferedDataSource::HttpInitialStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + } else { + // For all other protocols, assume they support range request. We fetch + // the full range of the resource to obtain the instance size because + // we won't be served HTTP headers. + loader_ = CreateResourceLoader(-1, -1); + loader_->Start( + NewCallback(this, &BufferedDataSource::NonHttpInitialStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); + } +} + +void BufferedDataSource::ReadTask( + int64 position, int read_size, uint8* buffer, + media::DataSource::ReadCallback* read_callback) { + DCHECK(MessageLoop::current() == render_loop_); + + // If CleanupTask() was executed we should return immediately. We check this + // variable to prevent doing any actual work after clean up was done. We do + // not check |stop_signal_received_| because anything use of it has to be + // within |lock_| which is not desirable. + if (stopped_on_render_loop_) + return; + + DCHECK(!read_callback_.get()); + DCHECK(read_callback); + + // Saves the read parameters. + read_position_ = position; + read_size_ = read_size; + read_callback_.reset(read_callback); + read_buffer_ = buffer; + read_submitted_time_ = base::Time::Now(); + read_attempts_ = 0; + + // Call to read internal to perform the actual read. + ReadInternal(); +} + +void BufferedDataSource::CleanupTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!stopped_on_render_loop_); + + // Stop the watch dog. + watch_dog_timer_.Stop(); + + // We just need to stop the loader, so it stops activity. + if (loader_.get()) + loader_->Stop(); + + // Reset the parameters of the current read request. + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; + read_submitted_time_ = base::Time(); + read_attempts_ = 0; + + // Signal that stop task has finished execution. + stopped_on_render_loop_ = true; +} + +void BufferedDataSource::RestartLoadingTask() { + DCHECK(MessageLoop::current() == render_loop_); + + // This variable is set in CleanupTask(). We check this and do an early + // return. The sequence of actions which enable this conditions is: + // 1. Stop() is called from the pipeline. + // 2. ReadCallback() is called from the resource loader. + // 3. CleanupTask() is executed. + // 4. RestartLoadingTask() is executed. + if (stopped_on_render_loop_) + return; + + // If there's no outstanding read then return early. + if (!read_callback_.get()) + return; + + loader_ = CreateResourceLoader(read_position_, -1); + loader_->SetAllowDefer(!media_is_paused_); + loader_->Start( + NewCallback(this, &BufferedDataSource::PartialReadStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); +} + +void BufferedDataSource::WatchDogTask() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(!stopped_on_render_loop_); + + // We only care if there is an active read request. + if (!read_callback_.get()) + return; + + DCHECK(loader_.get()); + base::TimeDelta delta = base::Time::Now() - read_submitted_time_; + if (delta < GetTimeoutMilliseconds()) + return; + + // TODO(hclam): Maybe raise an error here. But if an error is reported + // the whole pipeline may get destroyed... + if (read_attempts_ >= kReadTrials) + return; + + ++read_attempts_; + read_submitted_time_ = base::Time::Now(); + + // Stops the current loader and creates a new resource loader and + // retry the request. + loader_->Stop(); + loader_ = CreateResourceLoader(read_position_, -1); + loader_->SetAllowDefer(!media_is_paused_); + loader_->Start( + NewCallback(this, &BufferedDataSource::PartialReadStartCallback), + NewCallback(this, &BufferedDataSource::NetworkEventCallback)); +} + +void BufferedDataSource::SetPlaybackRateTask(float playback_rate) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + bool previously_paused = media_is_paused_; + media_is_paused_ = (playback_rate == 0.0); + + // Disallow deferring data when we are pausing, allow deferring data + // when we resume playing. + if (previously_paused && !media_is_paused_) { + loader_->SetAllowDefer(true); + } else if (!previously_paused && media_is_paused_) { + loader_->SetAllowDefer(false); + } +} + +// This method is the place where actual read happens, |loader_| must be valid +// prior to make this method call. +void BufferedDataSource::ReadInternal() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // First we prepare the intermediate read buffer for BufferedResourceLoader + // to write to. + if (read_size_ > intermediate_read_buffer_size_) { + intermediate_read_buffer_.reset(new uint8[read_size_]); + } + + // Perform the actual read with BufferedResourceLoader. + loader_->Read(read_position_, read_size_, intermediate_read_buffer_.get(), + NewCallback(this, &BufferedDataSource::ReadCallback)); +} + +// Method to report the results of the current read request. Also reset all +// the read parameters. +void BufferedDataSource::DoneRead_Locked(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(read_callback_.get()); + lock_.AssertAcquired(); + + if (error >= 0) { + read_callback_->RunWithParams(Tuple1<size_t>(error)); + } else { + read_callback_->RunWithParams( + Tuple1<size_t>(static_cast<size_t>(media::DataSource::kReadError))); + } + + read_callback_.reset(); + read_position_ = 0; + read_size_ = 0; + read_buffer_ = 0; +} + +void BufferedDataSource::DoneInitialization_Locked() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(initialize_callback_.get()); + lock_.AssertAcquired(); + + initialize_callback_->Run(); + initialize_callback_.reset(); +} + +///////////////////////////////////////////////////////////////////////////// +// BufferedDataSource, callback methods. +// These methods are called on the render thread for the events reported by +// BufferedResourceLoader. +void BufferedDataSource::HttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + int64 instance_size = loader_->instance_size(); + bool partial_response = loader_->partial_response(); + bool success = error == net::OK; + + if (success) { + // TODO(hclam): Needs more thinking about supporting servers without range + // request or their partial response is not complete. + total_bytes_ = instance_size; + loaded_ = false; + streaming_ = (instance_size == kPositionNotSpecified) || !partial_response; + } else { + // TODO(hclam): In case of failure, we can retry several times. + loader_->Stop(); + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (!success) { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + DoneInitialization_Locked(); + return; + } + + if (streaming_) { + // If the server didn't reply with an instance size, it is likely this + // is a streaming response. + host()->SetStreaming(true); + } else { + // This value governs the range that we can seek to. + // TODO(hclam): Report the correct value of buffered bytes. + host()->SetTotalBytes(total_bytes_); + host()->SetBufferedBytes(0); + } + + // Currently, only files can be used reliably w/o a network. + host()->SetLoaded(false); + DoneInitialization_Locked(); +} + +void BufferedDataSource::NonHttpInitialStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + int64 instance_size = loader_->instance_size(); + bool success = error == net::OK && instance_size != kPositionNotSpecified; + + if (success) { + total_bytes_ = instance_size; + loaded_ = true; + } else { + loader_->Stop(); + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. The only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (success) { + host()->SetTotalBytes(total_bytes_); + host()->SetBufferedBytes(total_bytes_); + host()->SetLoaded(loaded_); + } else { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + } + DoneInitialization_Locked(); +} + +void BufferedDataSource::PartialReadStartCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // This callback method is invoked after we have verified the server has + // range request capability, so as a safety guard verify again the response + // is partial. + if (error == net::OK && loader_->partial_response()) { + // Once the range request has started successfully, we can proceed with + // reading from it. + ReadInternal(); + return; + } + + // Stop the resource loader since we have received an error. + loader_->Stop(); + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is + // safe because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + DoneRead_Locked(net::ERR_INVALID_RESPONSE); +} + +void BufferedDataSource::ReadCallback(int error) { + DCHECK(MessageLoop::current() == render_loop_); + + if (error < 0) { + DCHECK(loader_.get()); + + // Stop the resource load if it failed. + loader_->Stop(); + + if (error == net::ERR_CACHE_MISS) { + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &BufferedDataSource::RestartLoadingTask)); + return; + } + } + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (error > 0) { + // If a position error code is received, read was successful. So copy + // from intermediate read buffer to the target read buffer. + memcpy(read_buffer_, intermediate_read_buffer_.get(), error); + } + DoneRead_Locked(error); +} + +void BufferedDataSource::NetworkEventCallback() { + DCHECK(MessageLoop::current() == render_loop_); + DCHECK(loader_.get()); + + // In case of non-HTTP request we don't need to report network events, + // so return immediately. + if (loaded_) + return; + + bool network_activity = loader_->network_activity(); + int64 buffered_last_byte_position = loader_->GetBufferedLastBytePosition(); + + // If we get an unspecified value, return immediately. + if (buffered_last_byte_position == kPositionNotSpecified) + return; + + // We need to prevent calling to filter host and running the callback if + // we have received the stop signal. We need to lock down the whole callback + // method to prevent bad things from happening. The reason behind this is + // that we cannot guarantee tasks on render thread have completely stopped + // when we receive the Stop() method call. So only way to solve this is to + // let tasks on render thread to run but make sure they don't call outside + // this object when Stop() method is ever called. Locking this method is safe + // because |lock_| is only acquired in tasks on render thread. + AutoLock auto_lock(lock_); + if (stop_signal_received_) + return; + + if (network_activity != network_activity_) { + network_activity_ = network_activity; + host()->SetNetworkActivity(network_activity); + } + host()->SetBufferedBytes(buffered_last_byte_position + 1); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/buffered_data_source.h b/webkit/glue/media/buffered_data_source.h new file mode 100644 index 0000000..0dc2115 --- /dev/null +++ b/webkit/glue/media/buffered_data_source.h @@ -0,0 +1,413 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ +#define WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "base/condition_variable.h" +#include "googleurl/src/gurl.h" +#include "media/base/factory.h" +#include "media/base/filters.h" +#include "media/base/media_format.h" +#include "media/base/pipeline.h" +#include "media/base/seekable_buffer.h" +#include "net/base/completion_callback.h" +#include "net/base/file_stream.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +namespace webkit_glue { +///////////////////////////////////////////////////////////////////////////// +// BufferedResourceLoader +// This class works inside demuxer thread and render thread. It contains a +// resource loader bridge and does the actual resource loading. This object +// does buffering internally, it defers the resource loading if buffer is +// full and un-defers the resource loading if it is under buffered. +class BufferedResourceLoader : + public base::RefCountedThreadSafe<BufferedResourceLoader>, + public webkit_glue::ResourceLoaderBridge::Peer { + public: + typedef Callback0::Type NetworkEventCallback; + + // |bridge_factory| - Factory to create a ResourceLoaderBridge. + // |url| - URL for the resource to be loaded. + // |first_byte_position| - First byte to start loading from, -1 for not + // specified. + // |last_byte_position| - Last byte to be loaded, -1 for not specified. + BufferedResourceLoader( + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory, + const GURL& url, + int64 first_byte_position, + int64 last_byte_position); + + // Start the resource loading with the specified URL and range. + // This method operates in asynchronous mode. Once there's a response from the + // server, success or fail |callback| is called with the result. + // |callback| is called with the following values: + // - net::OK + // The request has started successfully. + // - net::ERR_FAILED + // The request has failed because of an error with the network. + // - net::ERR_INVALID_RESPONSE + // An invalid response is received from the server. + // - (Anything else) + // An error code that indicates the request has failed. + // |event_callback| is called when the response is completed, data is + // received, the request is suspended or resumed. + virtual void Start(net::CompletionCallback* callback, + NetworkEventCallback* event_callback); + + // Stop this loader, cancels and request and release internal buffer. + virtual void Stop(); + + // Reads the specified |read_size| from |position| into |buffer| and when + // the operation is done invoke |callback| with number of bytes read or an + // error code. + // |callback| is called with the following values: + // - (Anything greater than or equal 0) + // Read was successful with the indicated number of bytes read. + // - net::ERR_FAILED + // The read has failed because of an error with the network. + // - net::ERR_CACHE_MISS + // The read was made too far away from the current buffered position. + virtual void Read(int64 position, int read_size, + uint8* buffer, net::CompletionCallback* callback); + + // Returns the position of the first byte buffered. Returns -1 if such value + // is not available. + virtual int64 GetBufferedFirstBytePosition(); + + // Returns the position of the last byte buffered. Returns -1 if such value + // is not available. + virtual int64 GetBufferedLastBytePosition(); + + // Sets whether deferring data is allowed or disallowed. + virtual void SetAllowDefer(bool is_allowed); + + // Gets the content length in bytes of the instance after this loader has been + // started. If this value is -1, then content length is unknown. + virtual int64 content_length() { return content_length_; } + + // Gets the original size of the file requested. If this value is -1, then + // the size is unknown. + virtual int64 instance_size() { return instance_size_; } + + // Returns true if the response for this loader is a partial response. + // It means a 206 response in HTTP/HTTPS protocol. + virtual bool partial_response() { return partial_response_; } + + // Returns true if network is currently active. + virtual bool network_activity() { return !completed_ && !deferred_; } + + ///////////////////////////////////////////////////////////////////////////// + // webkit_glue::ResourceLoaderBridge::Peer implementations. + virtual void OnUploadProgress(uint64 position, uint64 size) {} + virtual bool OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies); + virtual void OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered); + virtual void OnReceivedData(const char* data, int len); + virtual void OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info); + GURL GetURLForDebugging() const { return url_; } + + protected: + friend class base::RefCountedThreadSafe<BufferedResourceLoader>; + + virtual ~BufferedResourceLoader(); + + private: + friend class BufferedResourceLoaderTest; + + // Defer the resource loading if the buffer is full. + void EnableDeferIfNeeded(); + + // Disable defer loading if we are under-buffered. + void DisableDeferIfNeeded(); + + // Returns true if the current read request can be fulfilled by what is in + // the buffer. + bool CanFulfillRead(); + + // Returns true if the current read request will be fulfilled in the future. + bool WillFulfillRead(); + + // Method that does the actual read and calls the |read_callbac_|, assuming + // the request range is in |buffer_|. + void ReadInternal(); + + // If we have made a range request, verify the response from the server. + bool VerifyPartialResponse(const ResourceLoaderBridge::ResponseInfo& info); + + // Done with read. Invokes the read callback and reset parameters for the + // read request. + void DoneRead(int error); + + // Done with start. Invokes the start callback and reset it. + void DoneStart(int error); + + // Calls |event_callback_| in terms of a network event. + void NotifyNetworkEvent(); + + bool HasPendingRead() { return read_callback_.get() != NULL; } + + // A sliding window of buffer. + scoped_ptr<media::SeekableBuffer> buffer_; + + // True if resource loading was deferred. + bool deferred_; + + // True if resource loader is allowed to defer, false otherwise. + bool defer_allowed_; + + // True if resource loading has completed. + bool completed_; + + // True if a range request was made. + bool range_requested_; + + // True if response data received is a partial range. + bool partial_response_; + + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory_; + GURL url_; + int64 first_byte_position_; + int64 last_byte_position_; + + // Callback method that listens to network events. + scoped_ptr<NetworkEventCallback> event_callback_; + + // Members used during request start. + scoped_ptr<net::CompletionCallback> start_callback_; + scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_; + int64 offset_; + int64 content_length_; + int64 instance_size_; + + // Members used during a read operation. They should be reset after each + // read has completed or failed. + scoped_ptr<net::CompletionCallback> read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + + // Offsets of the requested first byte and last byte in |buffer_|. They are + // written by VerifyRead(). + int first_offset_; + int last_offset_; + + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoader); +}; + +class BufferedDataSource : public media::DataSource { + public: + // Methods called from pipeline thread + // Static methods for creating this class. + static media::FilterFactory* CreateFactory( + MessageLoop* message_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) { + return new media::FilterFactoryImpl2< + BufferedDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>( + message_loop, bridge_factory); + } + + // media::FilterFactoryImpl2 implementation. + static bool IsMediaFormatSupported( + const media::MediaFormat& media_format); + + // media::MediaFilter implementation. + virtual void Initialize(const std::string& url, + media::FilterCallback* callback); + virtual void Stop(media::FilterCallback* callback); + virtual void SetPlaybackRate(float playback_rate); + + // media::DataSource implementation. + // Called from demuxer thread. + virtual void Read(int64 position, size_t size, + uint8* data, + media::DataSource::ReadCallback* read_callback); + virtual bool GetSize(int64* size_out); + virtual bool IsStreaming(); + + const media::MediaFormat& media_format() { + return media_format_; + } + + protected: + BufferedDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory); + virtual ~BufferedDataSource(); + + // A factory method to create a BufferedResourceLoader based on the read + // parameters. We can override this file to object a mock + // BufferedResourceLoader for testing. + virtual BufferedResourceLoader* CreateResourceLoader( + int64 first_byte_position, int64 last_byte_position); + + // Gets the number of milliseconds to declare a request timeout since + // the request was made. This method is made virtual so as to inject a + // different number for testing purpose. + virtual base::TimeDelta GetTimeoutMilliseconds(); + + private: + friend class media::FilterFactoryImpl2< + BufferedDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>; + + // Posted to perform initialization on render thread and start resource + // loading. + void InitializeTask(); + + // Task posted to perform actual reading on the render thread. + void ReadTask(int64 position, int read_size, uint8* read_buffer, + media::DataSource::ReadCallback* read_callback); + + // Task posted when Stop() is called. Stops |watch_dog_timer_| and + // |loader_|, reset Read() variables, and set |stopped_on_render_loop_| + // to signal any remaining tasks to stop. + void CleanupTask(); + + // Restart resource loading on render thread. + void RestartLoadingTask(); + + // This task monitors the current active read request. If the current read + // request has timed out, this task will destroy the current loader and + // creates a new one to accommodate the read request. + void WatchDogTask(); + + // This task uses the current playback rate with the previous playback rate + // to determine whether we are going from pause to play and play to pause, + // and signals the buffered resource loader accordingly. + void SetPlaybackRateTask(float playback_rate); + + // The method that performs actual read. This method can only be executed on + // the render thread. + void ReadInternal(); + + // Calls |read_callback_| and reset all read parameters. + void DoneRead_Locked(int error); + + // Calls |initialize_callback_| and reset it. + void DoneInitialization_Locked(); + + // Callback method for |loader_| if URL for the resource requested is using + // HTTP protocol. This method is called when response for initial request is + // received. + void HttpInitialStartCallback(int error); + + // Callback method for |loader_| if URL for the resource requested is using + // a non-HTTP protocol, e.g. local files. This method is called when response + // for initial request is received. + void NonHttpInitialStartCallback(int error); + + // Callback method to be passed to BufferedResourceLoader during range + // request. Once a resource request has started, this method will be called + // with the error code. This method will be executed on the thread + // BufferedResourceLoader lives, i.e. render thread. + void PartialReadStartCallback(int error); + + // Callback method for making a read request to BufferedResourceLoader. + // If data arrives or the request has failed, this method is called with + // the error code or the number of bytes read. + void ReadCallback(int error); + + // Callback method when a network event is received. + void NetworkEventCallback(); + + media::MediaFormat media_format_; + + // URL of the resource requested. + GURL url_; + + // Members for total bytes of the requested object. It is written once on + // render thread but may be read from any thread. However reading of this + // member is guaranteed to happen after it is first written, so we don't + // need to protect it. + int64 total_bytes_; + + // True if this data source is considered loaded. + bool loaded_; + + // This value will be true if this data source can only support streaming. + // i.e. range request is not supported. + bool streaming_; + + // A factory object to produce ResourceLoaderBridge. + scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_; + + // A resource loader for the media resource. + scoped_refptr<BufferedResourceLoader> loader_; + + // True if network is active. + bool network_activity_; + + // Callback method from the pipeline for initialization. + scoped_ptr<media::FilterCallback> initialize_callback_; + + // Read parameters received from the Read() method call. + scoped_ptr<media::DataSource::ReadCallback> read_callback_; + int64 read_position_; + int read_size_; + uint8* read_buffer_; + base::Time read_submitted_time_; + int read_attempts_; + + // This buffer is intermediate, we use it for BufferedResourceLoader to write + // to. And when read in BufferedResourceLoader is done, we copy data from + // this buffer to |read_buffer_|. The reason for an additional copy is that + // we don't own |read_buffer_|. But since the read operation is asynchronous, + // |read_buffer| can be destroyed at any time, so we only copy into + // |read_buffer| in the final step when it is safe. + // Memory is allocated for this member during initialization of this object + // because we want buffer to be passed into BufferedResourceLoader to be + // always non-null. And by initializing this member with a default size we can + // avoid creating zero-sized buffered if the first read has zero size. + scoped_array<uint8> intermediate_read_buffer_; + int intermediate_read_buffer_size_; + + // The message loop of the render thread. + MessageLoop* render_loop_; + + // Protects |stopped_|. + Lock lock_; + + // Stop signal to suppressing activities. This variable is set on the pipeline + // thread and read from the render thread. + bool stop_signal_received_; + + // This variable is set by CleanupTask() that indicates this object is stopped + // on the render thread. + bool stopped_on_render_loop_; + + // This variable is true when we are in a paused state and false when we + // are in a playing state. + bool media_is_paused_; + + // This timer is to run the WatchDogTask repeatedly. We use a timer instead + // of doing PostDelayedTask() reduce the extra reference held by the message + // loop. The RepeatingTimer does PostDelayedTask() internally, by using it + // the message loop doesn't hold a reference for the watch dog task. + base::RepeatingTimer<BufferedDataSource> watch_dog_timer_; + + DISALLOW_COPY_AND_ASSIGN(BufferedDataSource); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_BUFFERED_DATA_SOURCE_H_ diff --git a/webkit/glue/media/buffered_data_source_unittest.cc b/webkit/glue/media/buffered_data_source_unittest.cc new file mode 100644 index 0000000..ce42437 --- /dev/null +++ b/webkit/glue/media/buffered_data_source_unittest.cc @@ -0,0 +1,934 @@ +// Copyright (c) 2010 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 <algorithm> + +#include "base/callback.h" +#include "base/format_macros.h" +#include "base/string_util.h" +#include "media/base/filters.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/glue/media/buffered_data_source.h" +#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h" +#include "webkit/glue/mock_resource_loader_bridge.h" + +using ::testing::_; +using ::testing::Assign; +using ::testing::DeleteArg; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::InvokeWithoutArgs; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::WithArgs; + +namespace { + +const char* kHttpUrl = "http://test"; +const char* kFileUrl = "file://test"; +const int kDataSize = 1024; + +enum NetworkState { + NONE, + LOADED, + LOADING +}; + +} // namespace + +namespace webkit_glue { + +// Submit a request completed event to the resource loader due to request +// being canceled. Pretending the event is from external. +ACTION_P(RequestCanceled, loader) { + URLRequestStatus status; + status.set_status(URLRequestStatus::CANCELED); + status.set_os_error(net::ERR_ABORTED); + loader->OnCompletedRequest(status, ""); +} + +class BufferedResourceLoaderTest : public testing::Test { + public: + BufferedResourceLoaderTest() { + bridge_.reset(new StrictMock<MockResourceLoaderBridge>()); + + for (int i = 0; i < kDataSize; ++i) + data_[i] = i; + } + + ~BufferedResourceLoaderTest() { + if (bridge_.get()) + EXPECT_CALL(*bridge_, OnDestroy()); + EXPECT_CALL(bridge_factory_, OnDestroy()); + } + + void Initialize(const char* url, int first_position, int last_position) { + gurl_ = GURL(url); + first_position_ = first_position; + last_position_ = last_position; + + loader_ = new BufferedResourceLoader(&bridge_factory_, gurl_, + first_position_, last_position_); + EXPECT_EQ(gurl_.spec(), loader_->GetURLForDebugging().spec()); + } + + void SetLoaderBuffer(size_t forward_capacity, size_t backward_capacity) { + loader_->buffer_.reset( + new media::SeekableBuffer(backward_capacity, forward_capacity)); + } + + void Start() { + InSequence s; + EXPECT_CALL(bridge_factory_, + CreateBridge(gurl_, _, first_position_, last_position_)) + .WillOnce(Return(bridge_.get())); + EXPECT_CALL(*bridge_, Start(loader_.get())); + loader_->Start( + NewCallback(this, &BufferedResourceLoaderTest::StartCallback), + NewCallback(this, &BufferedResourceLoaderTest::NetworkCallback)); + } + + void FullResponse(int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 200 OK\n" + "Content-Length: %" PRId64, + instance_size); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = instance_size; + loader_->OnReceivedResponse(info, false); + EXPECT_EQ(instance_size, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_FALSE(loader_->partial_response()); + } + + void PartialResponse(int64 first_position, int64 last_position, + int64 instance_size) { + EXPECT_CALL(*this, StartCallback(net::OK)); + int64 content_length = last_position - first_position + 1; + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n" + "Content-Range: bytes " + "%" PRId64 "-%" PRId64 "/%" PRId64, + first_position, + last_position, + instance_size); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = content_length; + loader_->OnReceivedResponse(info, false); + EXPECT_EQ(content_length, loader_->content_length()); + EXPECT_EQ(instance_size, loader_->instance_size()); + EXPECT_TRUE(loader_->partial_response()); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + loader_->Stop(); + } + + void ReleaseBridge() { + ignore_result(bridge_.release()); + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + EXPECT_CALL(*this, NetworkCallback()) + .RetiresOnSaturation(); + loader_->OnReceivedData(reinterpret_cast<char*>(data_ + position), size); + } + + // Helper method to read from |loader_|. + void ReadLoader(int64 position, int size, uint8* buffer) { + loader_->Read(position, size, buffer, + NewCallback(this, &BufferedResourceLoaderTest::ReadCallback)); + } + + // Verifis that data in buffer[0...size] is equal to data_[pos...pos+size]. + void VerifyBuffer(uint8* buffer, int pos, int size) { + EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); + } + + // Helper method to disallow deferring in |loader_|. + void DisallowLoaderDefer() { + if (loader_->deferred_) { + EXPECT_CALL(*bridge_, SetDefersLoading(false)); + EXPECT_CALL(*this, NetworkCallback()); + } + loader_->SetAllowDefer(false); + } + + // Helper method to allow deferring in |loader_|. + void AllowLoaderDefer() { + loader_->SetAllowDefer(true); + } + + MOCK_METHOD1(StartCallback, void(int error)); + MOCK_METHOD1(ReadCallback, void(int error)); + MOCK_METHOD0(NetworkCallback, void()); + + protected: + GURL gurl_; + int64 first_position_; + int64 last_position_; + + scoped_refptr<BufferedResourceLoader> loader_; + StrictMock<MockMediaResourceLoaderBridgeFactory> bridge_factory_; + scoped_ptr<StrictMock<MockResourceLoaderBridge> > bridge_; + + uint8 data_[kDataSize]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedResourceLoaderTest); +}; + +TEST_F(BufferedResourceLoaderTest, StartStop) { + Initialize(kHttpUrl, -1, -1); + Start(); + StopWhenLoad(); +} + +// Tests that HTTP header is missing in the response. +TEST_F(BufferedResourceLoaderTest, MissingHttpHeader) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + loader_->OnReceivedResponse(info, false); +} + +// Tests that a bad HTTP response is recived, e.g. file not found. +TEST_F(BufferedResourceLoaderTest, BadHttpResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_FAILED)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + info.headers = new net::HttpResponseHeaders("HTTP/1.1 404 Not Found\n"); + loader_->OnReceivedResponse(info, false); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(BufferedResourceLoaderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a 200 response is received. +TEST_F(BufferedResourceLoaderTest, FullResponse) { + Initialize(kHttpUrl, -1, -1); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a partial content response is received. +TEST_F(BufferedResourceLoaderTest, PartialResponse) { + Initialize(kHttpUrl, 100, 200); + Start(); + PartialResponse(100, 200, 1024); + StopWhenLoad(); +} + +// Tests that an invalid partial response is received. +TEST_F(BufferedResourceLoaderTest, InvalidPartialResponse) { + Initialize(kHttpUrl, 0, 10); + Start(); + + EXPECT_CALL(*this, StartCallback(net::ERR_INVALID_RESPONSE)); + EXPECT_CALL(*bridge_, Cancel()) + .WillOnce(RequestCanceled(loader_)); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + + ResourceLoaderBridge::ResponseInfo info; + std::string header = StringPrintf("HTTP/1.1 206 Partial Content\n" + "Content-Range: bytes %d-%d/%d", + 1, 10, 1024); + replace(header.begin(), header.end(), '\n', '\0'); + info.headers = new net::HttpResponseHeaders(header); + info.content_length = 10; + loader_->OnReceivedResponse(info, false); +} + +// Tests the logic of sliding window for data buffering and reading. +TEST_F(BufferedResourceLoaderTest, BufferAndRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + // Writes 10 bytes and read them back. + WriteLoader(10, 10); + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Writes 10 bytes and read 2 times. + WriteLoader(20, 10); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(20, 5, buffer); + VerifyBuffer(buffer, 20, 5); + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 5, buffer); + VerifyBuffer(buffer, 25, 5); + + // Read backward within buffer. + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + + // Read backward outside buffer. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(9, 10, buffer); + + // Response has completed. + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + loader_->OnCompletedRequest(status, ""); + + // Try to read 10 from position 25 will just return with 5 bytes. + EXPECT_CALL(*this, ReadCallback(5)); + ReadLoader(25, 10, buffer); + VerifyBuffer(buffer, 25, 5); + + // Try to read outside buffered range after request has completed. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(5, 10, buffer); + + // Try to read beyond the instance size. + EXPECT_CALL(*this, ReadCallback(0)); + ReadLoader(30, 10, buffer); +} + +TEST_F(BufferedResourceLoaderTest, ReadOutsideBuffer) { + Initialize(kHttpUrl, 10, 0x00FFFFFF); + Start(); + PartialResponse(10, 0x00FFFFFF, 0x01000000); + + uint8 buffer[10]; + InSequence s; + + // Read very far aheard will get a cache miss. + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(0x00FFFFFF, 1, buffer); + + // The following call will not call ReadCallback() because it is waiting for + // data to arrive. + ReadLoader(10, 10, buffer); + + // Writing to loader will fulfill the read request. + EXPECT_CALL(*this, ReadCallback(10)); + WriteLoader(10, 20); + VerifyBuffer(buffer, 10, 10); + + // The following call cannot be fulfilled now. + ReadLoader(25, 10, buffer); + + EXPECT_CALL(*this, ReadCallback(5)); + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + loader_->OnCompletedRequest(status, ""); +} + +TEST_F(BufferedResourceLoaderTest, RequestFailedWhenRead) { + Initialize(kHttpUrl, 10, 29); + Start(); + PartialResponse(10, 29, 30); + + uint8 buffer[10]; + InSequence s; + + ReadLoader(10, 10, buffer); + EXPECT_CALL(*this, ReadCallback(net::ERR_FAILED)); + EXPECT_CALL(*this, NetworkCallback()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &BufferedResourceLoaderTest::ReleaseBridge)); + URLRequestStatus status; + status.set_status(URLRequestStatus::FAILED); + loader_->OnCompletedRequest(status, ""); +} + +// Tests the logic of caching data to disk when media is paused. +TEST_F(BufferedResourceLoaderTest, AllowDefer_NoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + // Start in undeferred state, then disallow defer, then allow defer + // without receiving data in between. + DisallowLoaderDefer(); + AllowLoaderDefer(); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in undeferred state, disallow defer, receive data but don't shift + // buffer window, then allow defer and read. + DisallowLoaderDefer(); + WriteLoader(10, 10); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(10, 10, buffer); + VerifyBuffer(buffer, 10, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_ReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Not deferred, disallow defer, received data and shift buffer window, + // allow defer, then read in area outside of buffer window. + DisallowLoaderDefer(); + WriteLoader(10, 10); + WriteLoader(20, 50); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(10, 10, buffer); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredNoDataReceived) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, then disallow defer, receive no data, and + // allow defer and read. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadSameWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read in a place that's still in the window. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 30); + + DisallowLoaderDefer(); + WriteLoader(40, 5); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(10)); + ReadLoader(20, 10, buffer); + VerifyBuffer(buffer, 20, 10); + StopWhenLoad(); +} + +TEST_F(BufferedResourceLoaderTest, AllowDefer_DeferredReadPastWindow) { + Initialize(kHttpUrl, 10, 99); + SetLoaderBuffer(10, 20); + Start(); + PartialResponse(10, 99, 100); + + uint8 buffer[10]; + + // Start in deferred state, disallow defer, receive data and shift buffer + // window, allow defer, and read outside of the buffer window. + EXPECT_CALL(*bridge_, SetDefersLoading(true)); + EXPECT_CALL(*this, NetworkCallback()); + WriteLoader(10, 40); + + DisallowLoaderDefer(); + WriteLoader(50, 20); + WriteLoader(70, 40); + AllowLoaderDefer(); + + EXPECT_CALL(*this, ReadCallback(net::ERR_CACHE_MISS)); + ReadLoader(20, 5, buffer); + StopWhenLoad(); +} + +// TODO(hclam): add unit test for defer loading. + +class MockBufferedResourceLoader : public BufferedResourceLoader { + public: + MockBufferedResourceLoader() : BufferedResourceLoader(NULL, GURL(), 0, 0) { + } + + MOCK_METHOD2(Start, void(net::CompletionCallback* read_callback, + NetworkEventCallback* network_callback)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD4(Read, void(int64 position, int read_size, uint8* buffer, + net::CompletionCallback* callback)); + MOCK_METHOD0(content_length, int64()); + MOCK_METHOD0(instance_size, int64()); + MOCK_METHOD0(partial_response, bool()); + MOCK_METHOD0(network_activity, bool()); + MOCK_METHOD0(GetBufferedFirstBytePosition, int64()); + MOCK_METHOD0(GetBufferedLastBytePosition, int64()); + + protected: + ~MockBufferedResourceLoader() {} + + DISALLOW_COPY_AND_ASSIGN(MockBufferedResourceLoader); +}; + +// A mock BufferedDataSource to inject mock BufferedResourceLoader through +// CreateResourceLoader() method. +class MockBufferedDataSource : public BufferedDataSource { + public: + // Static methods for creating this class. + static media::FilterFactory* CreateFactory( + MessageLoop* message_loop, + MediaResourceLoaderBridgeFactory* bridge_factory) { + return new media::FilterFactoryImpl2< + MockBufferedDataSource, + MessageLoop*, + MediaResourceLoaderBridgeFactory*>(message_loop, + bridge_factory); + } + + virtual base::TimeDelta GetTimeoutMilliseconds() { + // It is 100 ms because we don't want the test to run too long. + return base::TimeDelta::FromMilliseconds(100); + } + + MOCK_METHOD2(CreateResourceLoader, BufferedResourceLoader*( + int64 first_position, int64 last_position)); + + protected: + MockBufferedDataSource( + MessageLoop* message_loop, MediaResourceLoaderBridgeFactory* factory) + : BufferedDataSource(message_loop, factory) { + } + + private: + friend class media::FilterFactoryImpl2< + MockBufferedDataSource, + MessageLoop*, + MediaResourceLoaderBridgeFactory*>; + + DISALLOW_COPY_AND_ASSIGN(MockBufferedDataSource); +}; + +class BufferedDataSourceTest : public testing::Test { + public: + BufferedDataSourceTest() { + message_loop_ = MessageLoop::current(); + bridge_factory_.reset( + new StrictMock<MockMediaResourceLoaderBridgeFactory>()); + factory_ = MockBufferedDataSource::CreateFactory(message_loop_, + bridge_factory_.get()); + + // Prepare test data. + for (size_t i = 0; i < sizeof(data_); ++i) { + data_[i] = i; + } + } + + virtual ~BufferedDataSourceTest() { + if (data_source_) { + // Release the bridge factory because we don't own it. + // Expects bridge factory to be destroyed along with data source. + EXPECT_CALL(*bridge_factory_, OnDestroy()) + .WillOnce(Invoke(this, + &BufferedDataSourceTest::ReleaseBridgeFactory)); + } + } + + void InitializeDataSource(const char* url, int error, + bool partial_response, int64 instance_size, + NetworkState networkState) { + // Saves the url first. + gurl_ = GURL(url); + + media::MediaFormat url_format; + url_format.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kURL); + url_format.SetAsString(media::MediaFormat::kURL, url); + data_source_ = factory_->Create<MockBufferedDataSource>(url_format); + CHECK(data_source_); + + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + + // Creates the mock loader to be injected. + loader_ = new StrictMock<MockBufferedResourceLoader>(); + + bool loaded = networkState == LOADED; + { + InSequence s; + EXPECT_CALL(*data_source_, CreateResourceLoader(_, _)) + .WillOnce(Return(loader_.get())); + + // The initial response loader will be started. + EXPECT_CALL(*loader_, Start(NotNull(), NotNull())) + .WillOnce( + DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + } + + StrictMock<media::MockFilterCallback> callback; + EXPECT_CALL(*loader_, instance_size()) + .WillRepeatedly(Return(instance_size)); + EXPECT_CALL(*loader_, partial_response()) + .WillRepeatedly(Return(partial_response)); + if (error == net::OK) { + // Expected loaded or not. + EXPECT_CALL(host_, SetLoaded(loaded)); + + // TODO(hclam): The condition for streaming needs to be adjusted. + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_CALL(host_, SetTotalBytes(instance_size)); + if (loaded) + EXPECT_CALL(host_, SetBufferedBytes(instance_size)); + else + EXPECT_CALL(host_, SetBufferedBytes(0)); + } else { + EXPECT_CALL(host_, SetStreaming(true)); + } + + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + } else { + EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); + EXPECT_CALL(*loader_, Stop()); + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + } + + // Actual initialization of the data source. + data_source_->Initialize(url, callback.NewCallback()); + message_loop_->RunAllPending(); + + if (error == net::OK) { + // Verify the size of the data source. + int64 size; + if (instance_size != -1 && (loaded || partial_response)) { + EXPECT_TRUE(data_source_->GetSize(&size)); + EXPECT_EQ(instance_size, size); + } else { + EXPECT_TRUE(data_source_->IsStreaming()); + } + } + } + + void StopDataSource() { + if (loader_) { + InSequence s; + EXPECT_CALL(*loader_, Stop()); + } + + StrictMock<media::MockFilterCallback> callback; + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + data_source_->Stop(callback.NewCallback()); + message_loop_->RunAllPending(); + } + + void ReleaseBridgeFactory() { + ignore_result(bridge_factory_.release()); + } + + void InvokeStartCallback( + net::CompletionCallback* callback, + BufferedResourceLoader::NetworkEventCallback* network_callback) { + callback->RunWithParams(Tuple1<int>(error_)); + delete callback; + // TODO(hclam): Save this callback. + delete network_callback; + } + + void InvokeReadCallback(int64 position, int size, uint8* buffer, + net::CompletionCallback* callback) { + if (error_ > 0) + memcpy(buffer, data_ + static_cast<int>(position), error_); + callback->RunWithParams(Tuple1<int>(error_)); + delete callback; + } + + void ReadDataSourceHit(int64 position, int size, int read_size) { + EXPECT_TRUE(loader_); + + InSequence s; + // Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, read_size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // The read has succeeded, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(read_size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + message_loop_->RunAllPending(); + + // Make sure data is correct. + EXPECT_EQ(0, + memcmp(buffer_, data_ + static_cast<int>(position), read_size)); + } + + void ReadDataSourceMiss(int64 position, int size) { + EXPECT_TRUE(loader_); + + // 1. Reply with a cache miss for the read. + { + InSequence s; + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::ERR_CACHE_MISS), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + EXPECT_CALL(*loader_, Stop()); + } + + // 2. Then the current loader will be stop and destroyed. + StrictMock<MockBufferedResourceLoader> *new_loader = + new StrictMock<MockBufferedResourceLoader>(); + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillOnce(Return(new_loader)); + + // 3. Then the new loader will be started. + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::OK), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + EXPECT_CALL(*new_loader, partial_response()) + .WillRepeatedly(Return(loader_->partial_response())); + + // 4. Then again a read request is made to the new loader. + EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + EXPECT_CALL(*this, ReadCallback(size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + message_loop_->RunAllPending(); + + // Make sure data is correct. + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); + + loader_ = new_loader; + } + + void ReadDataSourceFailed(int64 position, int size, int error) { + EXPECT_TRUE(loader_); + + // 1. Expect the read is delegated to the resource loader. + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, error), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback))); + + // 2. Host will then receive an error. + EXPECT_CALL(*loader_, Stop()); + + // 3. The read has failed, so read callback will be called. + EXPECT_CALL(*this, ReadCallback(media::DataSource::kReadError)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + + message_loop_->RunAllPending(); + } + + void ReadDataSourceTimesOut(int64 position, int size) { + // 1. Drop the request and let it times out. + { + InSequence s; + EXPECT_CALL(*loader_, Read(position, size, NotNull(), NotNull())) + .WillOnce(DeleteArg<3>()); + EXPECT_CALL(*loader_, Stop()); + } + + // 2. Then the current loader will be stop and destroyed. + StrictMock<MockBufferedResourceLoader> *new_loader = + new StrictMock<MockBufferedResourceLoader>(); + EXPECT_CALL(*data_source_, CreateResourceLoader(position, -1)) + .WillOnce(Return(new_loader)); + + // 3. Then the new loader will be started and respond to queries about + // whether this is a partial response using the value of the previous + // loader. + EXPECT_CALL(*new_loader, Start(NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, net::OK), + Invoke(this, + &BufferedDataSourceTest::InvokeStartCallback))); + EXPECT_CALL(*new_loader, partial_response()) + .WillRepeatedly(Return(loader_->partial_response())); + + // 4. Then again a read request is made to the new loader. + EXPECT_CALL(*new_loader, Read(position, size, NotNull(), NotNull())) + .WillOnce(DoAll(Assign(&error_, size), + Invoke(this, + &BufferedDataSourceTest::InvokeReadCallback), + InvokeWithoutArgs(message_loop_, + &MessageLoop::Quit))); + + EXPECT_CALL(*this, ReadCallback(size)); + + data_source_->Read( + position, size, buffer_, + NewCallback(this, &BufferedDataSourceTest::ReadCallback)); + + // This blocks the current thread until the watch task is executed and + // triggers a read callback to quit this message loop. + message_loop_->Run(); + + // Make sure data is correct. + EXPECT_EQ(0, memcmp(buffer_, data_ + static_cast<int>(position), size)); + + loader_ = new_loader; + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + scoped_ptr<StrictMock<MockMediaResourceLoaderBridgeFactory> > + bridge_factory_; + scoped_refptr<StrictMock<MockBufferedResourceLoader> > loader_; + scoped_refptr<MockBufferedDataSource> data_source_; + scoped_refptr<media::FilterFactory> factory_; + + StrictMock<media::MockFilterHost> host_; + GURL gurl_; + MessageLoop* message_loop_; + + int error_; + uint8 buffer_[1024]; + uint8 data_[1024]; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferedDataSourceTest); +}; + +TEST_F(BufferedDataSourceTest, InitializationSuccess) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, InitiailizationFailed) { + InitializeDataSource(kHttpUrl, net::ERR_FILE_NOT_FOUND, false, 0, NONE); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, MissingContentLength) { + InitializeDataSource(kHttpUrl, net::OK, true, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, RangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, 1024, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, + MissingContentLengthAndRangeRequestNotSupported) { + InitializeDataSource(kHttpUrl, net::OK, false, -1, LOADING); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheHit) { + InitializeDataSource(kHttpUrl, net::OK, true, 25, LOADING); + + // Performs read with cache hit. + ReadDataSourceHit(10, 10, 10); + + // Performs read with cache hit but partially filled. + ReadDataSourceHit(20, 10, 5); + + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadCacheMiss) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceMiss(1000, 10); + ReadDataSourceMiss(20, 10); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadFailed) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceHit(10, 10, 10); + ReadDataSourceFailed(10, 10, net::ERR_CONNECTION_RESET); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, ReadTimesOut) { + InitializeDataSource(kHttpUrl, net::OK, true, 1024, LOADING); + ReadDataSourceTimesOut(20, 10); + StopDataSource(); +} + +TEST_F(BufferedDataSourceTest, FileHasLoadedState) { + InitializeDataSource(kFileUrl, net::OK, true, 1024, LOADED); + ReadDataSourceTimesOut(20, 10); + StopDataSource(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.cc b/webkit/glue/media/media_resource_loader_bridge_factory.cc new file mode 100644 index 0000000..1961bcc --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +#include "base/format_macros.h" +#include "base/string_util.h" + +namespace { + +// A constant for an unknown position. +const int64 kPositionNotSpecified = -1; + +} // namespace + +namespace webkit_glue { + +MediaResourceLoaderBridgeFactory::MediaResourceLoaderBridgeFactory( + const GURL& referrer, + const std::string& frame_origin, + const std::string& main_frame_origin, + int origin_pid, + int appcache_host_id, + int32 routing_id) + : referrer_(referrer), + frame_origin_(frame_origin), + main_frame_origin_(main_frame_origin), + origin_pid_(origin_pid), + appcache_host_id_(appcache_host_id), + routing_id_(routing_id) { +} + +ResourceLoaderBridge* MediaResourceLoaderBridgeFactory::CreateBridge( + const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position) { + webkit_glue::ResourceLoaderBridge::RequestInfo request_info; + request_info.method = "GET"; + request_info.url = url; + request_info.first_party_for_cookies = url; + request_info.referrer = referrer_; + request_info.frame_origin = frame_origin_; + request_info.main_frame_origin = main_frame_origin_; + request_info.headers = GenerateHeaders(first_byte_position, + last_byte_position); + request_info.load_flags = load_flags; + request_info.requestor_pid = origin_pid_; + request_info.request_type = ResourceType::MEDIA; + request_info.appcache_host_id = appcache_host_id_; + request_info.routing_id = routing_id_; + return webkit_glue::ResourceLoaderBridge::Create(request_info); +} + +// static +const std::string MediaResourceLoaderBridgeFactory::GenerateHeaders ( + int64 first_byte_position, int64 last_byte_position) { + // Construct the range header. + std::string header; + if (first_byte_position > kPositionNotSpecified && + last_byte_position > kPositionNotSpecified) { + if (first_byte_position <= last_byte_position) { + header = StringPrintf("Range: bytes=%" PRId64 "-%" PRId64, + first_byte_position, + last_byte_position); + } + } else if (first_byte_position > kPositionNotSpecified) { + header = StringPrintf("Range: bytes=%" PRId64 "-", first_byte_position); + } else if (last_byte_position > kPositionNotSpecified) { + NOTIMPLEMENTED() << "Suffix range not implemented"; + } + return header; +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/media_resource_loader_bridge_factory.h b/webkit/glue/media/media_resource_loader_bridge_factory.h new file mode 100644 index 0000000..6408949 --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory.h @@ -0,0 +1,76 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ +#define WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ + +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "webkit/glue/resource_loader_bridge.h" + +namespace webkit_glue { + +// A factory used to create a ResourceLoaderBridge for the media player. +// This factory is used also for testing. Testing code can use this class and +// override CreateBridge() to inject a mock ResourceLoaderBridge for code that +// interacts with it, e.g. BufferedDataSource. +class MediaResourceLoaderBridgeFactory { + public: + MediaResourceLoaderBridgeFactory( + const GURL& referrer, + const std::string& frame_origin, + const std::string& main_frame_origin, + int origin_pid, + int appcache_host_id, + int32 routing_id); + + virtual ~MediaResourceLoaderBridgeFactory() {} + + // Factory method to create a ResourceLoaderBridge with the following + // parameters: + // |url| - URL of the resource to be loaded. + // |load_flags| - Load flags for this loading. + // |first_byte_position| - First byte position for a range request, -1 if not. + // |last_byte_position| - Last byte position for a range request, -1 if not. + virtual ResourceLoaderBridge* CreateBridge( + const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position); + + protected: + // An empty constructor only used by inherited classes. + MediaResourceLoaderBridgeFactory() { + } + + private: + FRIEND_TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders); + + // Returns a range request header using parameters |first_byte_position| and + // |last_byte_position|. + // Negative numbers other than -1 are not allowed for |first_byte_position| + // and |last_byte_position|. |first_byte_position| should always be less than + // or equal to |last_byte_position| if they are both not -1. + // Here's a list of valid parameters: + // |first_byte_position| |last_byte_position| + // 0 1000 + // 4096 4096 + // 0 -1 + // -1 -1 + // Empty string is returned on invalid parameters. + static const std::string GenerateHeaders(int64 first_byte_position, + int64 last_byte_position); + + GURL first_party_for_cookies_; + GURL referrer_; + std::string frame_origin_; + std::string main_frame_origin_; + std::string headers_; + int origin_pid_; + int appcache_host_id_; + int32 routing_id_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ diff --git a/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc new file mode 100644 index 0000000..4c0126b --- /dev/null +++ b/webkit/glue/media/media_resource_loader_bridge_factory_unittest.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_util.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace webkit_glue { + +TEST(MediaResourceLoaderBridgeFactoryTest, GenerateHeaders) { + static const struct { + const bool success; + const int64 first_byte_position; + const int64 last_byte_position; + } data[] = { + { false, -1, -1 }, + { false, -5, 0 }, + { false, 100, 0 }, + { true, 0, -1 }, + { true, 0, 0 }, + { true, 100, 100 }, + { true, 50, -1 }, + { true, 10000, -1 }, + { true, 50, 100 }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + std::string headers = MediaResourceLoaderBridgeFactory::GenerateHeaders( + data[i].first_byte_position, data[i].last_byte_position); + std::vector<net::HttpByteRange> ranges; + bool ret = net::HttpUtil::ParseRanges(headers, &ranges); + EXPECT_EQ(data[i].success, ret); + if (ret) { + EXPECT_EQ(1u, ranges.size()); + EXPECT_EQ(data[i].first_byte_position, + ranges[0].first_byte_position()); + EXPECT_EQ(data[i].last_byte_position, + ranges[0].last_byte_position()); + } + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/mock_media_resource_loader_bridge_factory.h b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h new file mode 100644 index 0000000..7bb27fe --- /dev/null +++ b/webkit/glue/media/mock_media_resource_loader_bridge_factory.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_ +#define WEBKIT_GLUE_MOCK_RESOURCE_LOADER_BRIDGE_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +namespace webkit_glue { + +class MockMediaResourceLoaderBridgeFactory + : public webkit_glue::MediaResourceLoaderBridgeFactory { + public: + MockMediaResourceLoaderBridgeFactory() { + } + + virtual ~MockMediaResourceLoaderBridgeFactory() { + OnDestroy(); + } + + MOCK_METHOD4(CreateBridge, + webkit_glue::ResourceLoaderBridge*(const GURL& url, + int load_flags, + int64 first_byte_position, + int64 last_byte_position)); + MOCK_METHOD0(OnDestroy, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockMediaResourceLoaderBridgeFactory); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_MOCK_RESOURCE_LOADER_BRIDGE_H_ diff --git a/webkit/glue/media/simple_data_source.cc b/webkit/glue/media/simple_data_source.cc new file mode 100644 index 0000000..20bf0af --- /dev/null +++ b/webkit/glue/media/simple_data_source.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2010 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/message_loop.h" +#include "base/process_util.h" +#include "media/base/filter_host.h" +#include "net/base/load_flags.h" +#include "net/base/data_url.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_status.h" +#include "webkit/glue/media/simple_data_source.h" +#include "webkit/glue/resource_loader_bridge.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const char kHttpScheme[] = "http"; +const char kHttpsScheme[] = "https"; +const char kDataScheme[] = "data"; + +// A helper method that accepts only HTTP, HTTPS and FILE protocol. +bool IsDataProtocol(const GURL& url) { + return url.SchemeIs(kDataScheme); +} + +} // namespace + +namespace webkit_glue { + +bool SimpleDataSource::IsMediaFormatSupported( + const media::MediaFormat& media_format) { + std::string mime_type; + std::string url; + if (media_format.GetAsString(media::MediaFormat::kMimeType, &mime_type) && + media_format.GetAsString(media::MediaFormat::kURL, &url)) { + GURL gurl(url); + if (IsProtocolSupportedForMedia(gurl)) + return true; + } + return false; +} + +SimpleDataSource::SimpleDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) + : render_loop_(render_loop), + bridge_factory_(bridge_factory), + size_(-1), + state_(UNINITIALIZED) { + DCHECK(render_loop); +} + +SimpleDataSource::~SimpleDataSource() { + AutoLock auto_lock(lock_); + DCHECK(state_ == UNINITIALIZED || state_ == STOPPED); +} + +void SimpleDataSource::Stop(media::FilterCallback* callback) { + AutoLock auto_lock(lock_); + state_ = STOPPED; + if (callback) { + callback->Run(); + delete callback; + } + + // Post a task to the render thread to cancel loading the resource. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SimpleDataSource::CancelTask)); +} + +void SimpleDataSource::Initialize(const std::string& url, + media::FilterCallback* callback) { + AutoLock auto_lock(lock_); + DCHECK_EQ(state_, UNINITIALIZED); + DCHECK(callback); + state_ = INITIALIZING; + initialize_callback_.reset(callback); + + // Validate the URL. + SetURL(GURL(url)); + if (!url_.is_valid() || !IsProtocolSupportedForMedia(url_)) { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + initialize_callback_->Run(); + initialize_callback_.reset(); + return; + } + + // Post a task to the render thread to start loading the resource. + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SimpleDataSource::StartTask)); +} + +const media::MediaFormat& SimpleDataSource::media_format() { + return media_format_; +} + +void SimpleDataSource::Read(int64 position, + size_t size, + uint8* data, + ReadCallback* read_callback) { + DCHECK_GE(size_, 0); + if (position >= size_) { + read_callback->RunWithParams(Tuple1<size_t>(0)); + delete read_callback; + } else { + size_t copied = std::min(size, static_cast<size_t>(size_ - position)); + memcpy(data, data_.c_str() + position, copied); + read_callback->RunWithParams(Tuple1<size_t>(copied)); + delete read_callback; + } +} + +bool SimpleDataSource::GetSize(int64* size_out) { + *size_out = size_; + return true; +} + +bool SimpleDataSource::IsStreaming() { + return false; +} + +void SimpleDataSource::OnDownloadProgress(uint64 position, uint64 size) {} + +void SimpleDataSource::OnUploadProgress(uint64 position, uint64 size) {} + +bool SimpleDataSource::OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + SetURL(new_url); + // TODO(wtc): should we return a new first party for cookies URL? + *has_new_first_party_for_cookies = false; + return true; +} + +void SimpleDataSource::OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + size_ = info.content_length; +} + +void SimpleDataSource::OnReceivedData(const char* data, int len) { + data_.append(data, len); +} + +void SimpleDataSource::OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info) { + AutoLock auto_lock(lock_); + // It's possible this gets called after Stop(), in which case |host_| is no + // longer valid. + if (state_ == STOPPED) { + return; + } + + // Otherwise we should be initializing and have created a bridge. + DCHECK_EQ(state_, INITIALIZING); + DCHECK(bridge_.get()); + bridge_.reset(); + + // If we don't get a content length or the request has failed, report it + // as a network error. + DCHECK(size_ == -1 || static_cast<size_t>(size_) == data_.length()); + if (size_ == -1) { + size_ = data_.length(); + } + + DoneInitialization_Locked(status.is_success()); +} + +GURL SimpleDataSource::GetURLForDebugging() const { + return url_; +} + +void SimpleDataSource::SetURL(const GURL& url) { + url_ = url; + media_format_.Clear(); + media_format_.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kApplicationOctetStream); + media_format_.SetAsString(media::MediaFormat::kURL, url.spec()); +} + +void SimpleDataSource::StartTask() { + AutoLock auto_lock(lock_); + DCHECK(MessageLoop::current() == render_loop_); + + // We may have stopped. + if (state_ == STOPPED) + return; + + DCHECK_EQ(state_, INITIALIZING); + + if (IsDataProtocol(url_)) { + // If this using data protocol, we just need to decode it. + std::string mime_type, charset; + bool success = net::DataURL::Parse(url_, &mime_type, &charset, &data_); + + // Don't care about the mime-type just proceed if decoding was successful. + size_ = data_.length(); + DoneInitialization_Locked(success); + } else { + // Create our bridge and start loading the resource. + bridge_.reset(bridge_factory_->CreateBridge( + url_, net::LOAD_BYPASS_CACHE, -1, -1)); + bridge_->Start(this); + } +} + +void SimpleDataSource::CancelTask() { + AutoLock auto_lock(lock_); + DCHECK_EQ(state_, STOPPED); + + // Cancel any pending requests. + if (bridge_.get()) { + bridge_->Cancel(); + bridge_.reset(); + } +} + +void SimpleDataSource::DoneInitialization_Locked(bool success) { + lock_.AssertAcquired(); + if (success) { + state_ = INITIALIZED; + host()->SetTotalBytes(size_); + host()->SetBufferedBytes(size_); + // If scheme is file or data, say we are loaded. + host()->SetLoaded(url_.SchemeIsFile() || IsDataProtocol(url_)); + } else { + host()->SetError(media::PIPELINE_ERROR_NETWORK); + } + initialize_callback_->Run(); + initialize_callback_.reset(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/simple_data_source.h b/webkit/glue/media/simple_data_source.h new file mode 100644 index 0000000..577d973 --- /dev/null +++ b/webkit/glue/media/simple_data_source.h @@ -0,0 +1,125 @@ +// Copyright (c) 2010 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. + +// An extremely simple implementation of DataSource that downloads the entire +// media resource into memory before signaling that initialization has finished. +// Primarily used to test <audio> and <video> with buffering/caching removed +// from the equation. + +#ifndef WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_ +#define WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_ + +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "media/base/factory.h" +#include "media/base/filters.h" +#include "webkit/glue/media/media_resource_loader_bridge_factory.h" + +class MessageLoop; +class WebMediaPlayerDelegateImpl; + +namespace webkit_glue { + +class SimpleDataSource : public media::DataSource, + public webkit_glue::ResourceLoaderBridge::Peer { + public: + static media::FilterFactory* CreateFactory( + MessageLoop* message_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory) { + return new media::FilterFactoryImpl2< + SimpleDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>(message_loop, + bridge_factory); + } + + // media::FilterFactoryImpl2 implementation. + static bool IsMediaFormatSupported( + const media::MediaFormat& media_format); + + // MediaFilter implementation. + virtual void Stop(media::FilterCallback* callback); + + // DataSource implementation. + virtual void Initialize(const std::string& url, + media::FilterCallback* callback); + virtual const media::MediaFormat& media_format(); + virtual void Read(int64 position, size_t size, + uint8* data, ReadCallback* read_callback); + virtual bool GetSize(int64* size_out); + virtual bool IsStreaming(); + + // webkit_glue::ResourceLoaderBridge::Peer implementation. + virtual void OnDownloadProgress(uint64 position, uint64 size); + virtual void OnUploadProgress(uint64 position, uint64 size); + virtual bool OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies); + virtual void OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered); + virtual void OnReceivedData(const char* data, int len); + virtual void OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info); + virtual GURL GetURLForDebugging() const; + + private: + friend class media::FilterFactoryImpl2< + SimpleDataSource, + MessageLoop*, + webkit_glue::MediaResourceLoaderBridgeFactory*>; + SimpleDataSource( + MessageLoop* render_loop, + webkit_glue::MediaResourceLoaderBridgeFactory* bridge_factory); + virtual ~SimpleDataSource(); + + // Updates |url_| and |media_format_| with the given URL. + void SetURL(const GURL& url); + + // Creates and starts the resource loading on the render thread. + void StartTask(); + + // Cancels and deletes the resource loading on the render thread. + void CancelTask(); + + // Perform initialization completion tasks under a lock. + void DoneInitialization_Locked(bool success); + + // Primarily used for asserting the bridge is loading on the render thread. + MessageLoop* render_loop_; + + // Factory to create a bridge. + scoped_ptr<webkit_glue::MediaResourceLoaderBridgeFactory> bridge_factory_; + + // Bridge used to load the media resource. + scoped_ptr<webkit_glue::ResourceLoaderBridge> bridge_; + + media::MediaFormat media_format_; + GURL url_; + std::string data_; + int64 size_; + + // Simple state tracking variable. + enum State { + UNINITIALIZED, + INITIALIZING, + INITIALIZED, + STOPPED, + }; + State state_; + + // Used for accessing |state_|. + Lock lock_; + + // Filter callbacks. + scoped_ptr<media::FilterCallback> initialize_callback_; + + DISALLOW_COPY_AND_ASSIGN(SimpleDataSource); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_SIMPLE_DATA_SOURCE_H_ diff --git a/webkit/glue/media/simple_data_source_unittest.cc b/webkit/glue/media/simple_data_source_unittest.cc new file mode 100644 index 0000000..e6acba9 --- /dev/null +++ b/webkit/glue/media/simple_data_source_unittest.cc @@ -0,0 +1,252 @@ +// Copyright (c) 2010 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/callback.h" +#include "media/base/filters.h" +#include "media/base/mock_filter_host.h" +#include "media/base/mock_filters.h" +#include "webkit/glue/media/mock_media_resource_loader_bridge_factory.h" +#include "webkit/glue/media/simple_data_source.h" +#include "webkit/glue/mock_resource_loader_bridge.h" + +using ::testing::_; +using ::testing::DoAll; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::StrictMock; +using ::testing::WithArgs; + +namespace { + +const int kDataSize = 1024; +const char kHttpUrl[] = "http://test"; +const char kHttpsUrl[] = "https://test"; +const char kFileUrl[] = "file://test"; +const char kDataUrl[] = + "data:text/plain;base64,YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoK"; +const char kDataUrlDecoded[] = "abcdefghijklmnopqrstuvwxyz"; +const char kInvalidUrl[] = "whatever://test"; + +} // namespace + +namespace webkit_glue { + +class SimpleDataSourceTest : public testing::Test { + public: + SimpleDataSourceTest() { + bridge_factory_.reset( + new NiceMock<MockMediaResourceLoaderBridgeFactory>()); + bridge_.reset(new NiceMock<MockResourceLoaderBridge>()); + factory_ = SimpleDataSource::CreateFactory(MessageLoop::current(), + bridge_factory_.get()); + + for (int i = 0; i < kDataSize; ++i) { + data_[i] = i; + } + } + + virtual ~SimpleDataSourceTest() { + if (bridge_.get()) + EXPECT_CALL(*bridge_, OnDestroy()); + if (bridge_factory_.get()) + EXPECT_CALL(*bridge_factory_, OnDestroy()); + } + + void InitializeDataSource(const char* url) { + media::MediaFormat url_format; + url_format.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kURL); + url_format.SetAsString(media::MediaFormat::kURL, url); + data_source_ = factory_->Create<SimpleDataSource>(url_format); + CHECK(data_source_); + + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + + // First a bridge is created. + InSequence s; + EXPECT_CALL(*bridge_factory_, CreateBridge(GURL(url), _, -1, -1)) + .WillOnce(Return(bridge_.get())); + EXPECT_CALL(*bridge_, Start(data_source_.get())) + .WillOnce(Return(true)); + + data_source_->Initialize(url, callback_.NewCallback()); + + MessageLoop::current()->RunAllPending(); + } + + void RequestSucceeded(bool is_loaded) { + ResourceLoaderBridge::ResponseInfo info; + info.content_length = kDataSize; + + data_source_->OnReceivedResponse(info, false); + int64 size; + EXPECT_TRUE(data_source_->GetSize(&size)); + EXPECT_EQ(kDataSize, size); + + for (int i = 0; i < kDataSize; ++i) + data_source_->OnReceivedData(data_ + i, 1); + + EXPECT_CALL(host_, SetLoaded(is_loaded)); + + InSequence s; + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); + EXPECT_CALL(host_, SetTotalBytes(kDataSize)); + EXPECT_CALL(host_, SetBufferedBytes(kDataSize)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + status.set_os_error(0); + data_source_->OnCompletedRequest(status, ""); + + // Let the tasks to be executed. + MessageLoop::current()->RunAllPending(); + } + + void RequestFailed() { + InSequence s; + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); + EXPECT_CALL(host_, SetError(media::PIPELINE_ERROR_NETWORK)); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + URLRequestStatus status; + status.set_status(URLRequestStatus::FAILED); + status.set_os_error(100); + data_source_->OnCompletedRequest(status, ""); + + // Let the tasks to be executed. + MessageLoop::current()->RunAllPending(); + } + + void DestroyDataSource() { + EXPECT_CALL(*bridge_factory_, OnDestroy()) + .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridgeFactory)); + + StrictMock<media::MockFilterCallback> callback; + EXPECT_CALL(callback, OnFilterCallback()); + EXPECT_CALL(callback, OnCallbackDestroyed()); + data_source_->Stop(callback.NewCallback()); + MessageLoop::current()->RunAllPending(); + + data_source_ = NULL; + } + + void AsyncRead() { + for (int i = 0; i < kDataSize; ++i) { + uint8 buffer[1]; + + EXPECT_CALL(*this, ReadCallback(1)); + data_source_->Read( + i, 1, buffer, + NewCallback(this, &SimpleDataSourceTest::ReadCallback)); + EXPECT_EQ(static_cast<uint8>(data_[i]), buffer[0]); + } + } + + void ReleaseBridge() { + ignore_result(bridge_.release()); + } + + void ReleaseBridgeFactory() { + ignore_result(bridge_factory_.release()); + } + + MOCK_METHOD1(ReadCallback, void(size_t size)); + + protected: + scoped_ptr<MessageLoop> message_loop_; + scoped_ptr<NiceMock<MockMediaResourceLoaderBridgeFactory> > bridge_factory_; + scoped_ptr<NiceMock<MockResourceLoaderBridge> > bridge_; + scoped_refptr<media::FilterFactory> factory_; + scoped_refptr<SimpleDataSource> data_source_; + StrictMock<media::MockFilterHost> host_; + StrictMock<media::MockFilterCallback> callback_; + char data_[kDataSize]; + + DISALLOW_COPY_AND_ASSIGN(SimpleDataSourceTest); +}; + +TEST_F(SimpleDataSourceTest, InitializeHTTP) { + InitializeDataSource(kHttpUrl); + RequestSucceeded(false); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeHTTPS) { + InitializeDataSource(kHttpsUrl); + RequestSucceeded(false); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeFile) { + InitializeDataSource(kFileUrl); + RequestSucceeded(true); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeData) { + media::MediaFormat url_format; + url_format.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kURL); + url_format.SetAsString(media::MediaFormat::kURL, kDataUrl); + data_source_ = factory_->Create<SimpleDataSource>(url_format); + CHECK(data_source_); + + // There is no need to provide a message loop to data source. + data_source_->set_host(&host_); + + EXPECT_CALL(host_, SetLoaded(true)); + EXPECT_CALL(host_, SetTotalBytes(sizeof(kDataUrlDecoded))); + EXPECT_CALL(host_, SetBufferedBytes(sizeof(kDataUrlDecoded))); + EXPECT_CALL(callback_, OnFilterCallback()); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + + data_source_->Initialize(kDataUrl, callback_.NewCallback()); + MessageLoop::current()->RunAllPending(); + + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, InitializeInvalid) { + media::MediaFormat url_format; + url_format.SetAsString(media::MediaFormat::kMimeType, + media::mime_type::kURL); + url_format.SetAsString(media::MediaFormat::kURL, kInvalidUrl); + data_source_ = factory_->Create<SimpleDataSource>(url_format); + EXPECT_FALSE(data_source_); +} + +TEST_F(SimpleDataSourceTest, RequestFailed) { + InitializeDataSource(kHttpUrl); + RequestFailed(); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, StopWhenDownloading) { + InitializeDataSource(kHttpUrl); + + EXPECT_CALL(*bridge_, Cancel()); + EXPECT_CALL(*bridge_, OnDestroy()) + .WillOnce(Invoke(this, &SimpleDataSourceTest::ReleaseBridge)); + EXPECT_CALL(callback_, OnCallbackDestroyed()); + DestroyDataSource(); +} + +TEST_F(SimpleDataSourceTest, AsyncRead) { + InitializeDataSource(kFileUrl); + RequestSucceeded(true); + AsyncRead(); + DestroyDataSource(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/video_renderer_impl.cc b/webkit/glue/media/video_renderer_impl.cc new file mode 100644 index 0000000..796d07f --- /dev/null +++ b/webkit/glue/media/video_renderer_impl.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2010 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 "webkit/glue/media/video_renderer_impl.h" + +#include "media/base/video_frame.h" +#include "media/base/yuv_convert.h" +#include "webkit/glue/webmediaplayer_impl.h" + +namespace webkit_glue { + +VideoRendererImpl::VideoRendererImpl(WebMediaPlayerImpl::Proxy* proxy, + bool pts_logging) + : proxy_(proxy), + last_converted_frame_(NULL), + pts_logging_(pts_logging) { + // TODO(hclam): decide whether to do the following line in this thread or + // in the render thread. + proxy_->SetVideoRenderer(this); +} + +// static +media::FilterFactory* VideoRendererImpl::CreateFactory( + WebMediaPlayerImpl::Proxy* proxy, + bool pts_logging) { + return new media::FilterFactoryImpl2<VideoRendererImpl, + WebMediaPlayerImpl::Proxy*, + bool>(proxy, pts_logging); +} + +// static +bool VideoRendererImpl::IsMediaFormatSupported( + const media::MediaFormat& media_format) { + return ParseMediaFormat(media_format, NULL, NULL, NULL, NULL); +} + +bool VideoRendererImpl::OnInitialize(media::VideoDecoder* decoder) { + video_size_.SetSize(width(), height()); + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width(), height()); + if (bitmap_.allocPixels(NULL, NULL)) { + bitmap_.eraseRGB(0x00, 0x00, 0x00); + return true; + } + + NOTREACHED(); + return false; +} + +void VideoRendererImpl::OnStop(media::FilterCallback* callback) { + if (callback) { + callback->Run(); + delete callback; + } +} + +void VideoRendererImpl::OnFrameAvailable() { + proxy_->Repaint(); +} + +void VideoRendererImpl::SetRect(const gfx::Rect& rect) { +} + +// This method is always called on the renderer's thread. +void VideoRendererImpl::Paint(skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) { + scoped_refptr<media::VideoFrame> video_frame; + GetCurrentFrame(&video_frame); + if (!video_frame) { + SkPaint paint; + paint.setColor(SK_ColorBLACK); + canvas->drawRectCoords( + static_cast<float>(dest_rect.x()), + static_cast<float>(dest_rect.y()), + static_cast<float>(dest_rect.right()), + static_cast<float>(dest_rect.bottom()), + paint); + } else { + if (CanFastPaint(canvas, dest_rect)) { + FastPaint(video_frame, canvas, dest_rect); + } else { + SlowPaint(video_frame, canvas, dest_rect); + } + + // Presentation timestamp logging is primarily used to measure performance + // on low-end devices. When profiled on an Intel Atom N280 @ 1.66GHz this + // code had a ~63 microsecond perf hit when logging to a file (not stdout), + // which is neglible enough for measuring playback performance. + if (pts_logging_) { + LOG(INFO) << "pts=" + << video_frame->GetTimestamp().InMicroseconds(); + } + } + + PutCurrentFrame(video_frame); +} + +// CanFastPaint is a helper method to determine the conditions for fast +// painting. The conditions are: +// 1. No skew in canvas matrix. +// 2. No flipping nor mirroring. +// 3. Canvas has pixel format ARGB8888. +// 4. Canvas is opaque. +// TODO(hclam): The fast paint method should support flipping and mirroring. +// Disable the flipping and mirroring checks once we have it. +bool VideoRendererImpl::CanFastPaint(skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) { + // Fast paint does not handle opacity value other than 1.0. Hence use slow + // paint if opacity is not 1.0. Since alpha = opacity * 0xFF, we check that + // alpha != 0xFF. + // + // Additonal notes: If opacity = 0.0, the chrome display engine does not try + // to render the video. So, this method is never called. However, if the + // opacity = 0.0001, alpha is again 0, but the display engine tries to render + // the video. If we use Fast paint, the video shows up with opacity = 1.0. + // Hence we use slow paint also in the case where alpha = 0. It would be ideal + // if rendering was never called even for cases where alpha is 0. Created + // bug 48090 for this. + SkCanvas::LayerIter layer_iter(canvas, false); + SkColor sk_color = layer_iter.paint().getColor(); + SkAlpha sk_alpha = SkColorGetA(sk_color); + if (sk_alpha != 0xFF) { + return false; + } + + const SkMatrix& total_matrix = canvas->getTotalMatrix(); + // Perform the following checks here: + // 1. Check for skewing factors of the transformation matrix. They should be + // zero. + // 2. Check for mirroring and flipping. Make sure they are greater than zero. + if (SkScalarNearlyZero(total_matrix.getSkewX()) && + SkScalarNearlyZero(total_matrix.getSkewY()) && + total_matrix.getScaleX() > 0 && + total_matrix.getScaleY() > 0) { + // Get the properties of the SkDevice and the clip rect. + SkDevice* device = canvas->getDevice(); + + // Get the boundary of the device. + SkIRect device_rect; + device->getBounds(&device_rect); + + // Get the pixel config of the device. + const SkBitmap::Config config = device->config(); + // Get the total clip rect associated with the canvas. + const SkRegion& total_clip = canvas->getTotalClip(); + + SkIRect dest_irect; + TransformToSkIRect(canvas->getTotalMatrix(), dest_rect, &dest_irect); + + if (config == SkBitmap::kARGB_8888_Config && device->isOpaque() && + device_rect.contains(total_clip.getBounds())) { + return true; + } + } + + return false; +} + +void VideoRendererImpl::SlowPaint(media::VideoFrame* video_frame, + skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) { + // 1. Convert YUV frame to RGB. + base::TimeDelta timestamp = video_frame->GetTimestamp(); + if (video_frame != last_converted_frame_ || + timestamp != last_converted_timestamp_) { + last_converted_frame_ = video_frame; + last_converted_timestamp_ = timestamp; + DCHECK(video_frame->format() == media::VideoFrame::YV12 || + video_frame->format() == media::VideoFrame::YV16); + DCHECK(video_frame->stride(media::VideoFrame::kUPlane) == + video_frame->stride(media::VideoFrame::kVPlane)); + DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes); + bitmap_.lockPixels(); + media::YUVType yuv_type = + (video_frame->format() == media::VideoFrame::YV12) ? + media::YV12 : media::YV16; + media::ConvertYUVToRGB32(video_frame->data(media::VideoFrame::kYPlane), + video_frame->data(media::VideoFrame::kUPlane), + video_frame->data(media::VideoFrame::kVPlane), + static_cast<uint8*>(bitmap_.getPixels()), + video_frame->width(), + video_frame->height(), + video_frame->stride(media::VideoFrame::kYPlane), + video_frame->stride(media::VideoFrame::kUPlane), + bitmap_.rowBytes(), + yuv_type); + bitmap_.unlockPixels(); + } + + // 2. Paint the bitmap to canvas. + SkMatrix matrix; + matrix.setTranslate(static_cast<SkScalar>(dest_rect.x()), + static_cast<SkScalar>(dest_rect.y())); + if (dest_rect.width() != video_size_.width() || + dest_rect.height() != video_size_.height()) { + matrix.preScale(SkIntToScalar(dest_rect.width()) / + SkIntToScalar(video_size_.width()), + SkIntToScalar(dest_rect.height()) / + SkIntToScalar(video_size_.height())); + } + SkPaint paint; + paint.setFlags(SkPaint::kFilterBitmap_Flag); + canvas->drawBitmapMatrix(bitmap_, matrix, &paint); +} + +void VideoRendererImpl::FastPaint(media::VideoFrame* video_frame, + skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) { + DCHECK(video_frame->format() == media::VideoFrame::YV12 || + video_frame->format() == media::VideoFrame::YV16); + DCHECK(video_frame->stride(media::VideoFrame::kUPlane) == + video_frame->stride(media::VideoFrame::kVPlane)); + DCHECK(video_frame->planes() == media::VideoFrame::kNumYUVPlanes); + const SkBitmap& bitmap = canvas->getDevice()->accessBitmap(true); + media::YUVType yuv_type = (video_frame->format() == media::VideoFrame::YV12) ? + media::YV12 : media::YV16; + int y_shift = yuv_type; // 1 for YV12, 0 for YV16. + + // Create a rectangle backed by SkScalar. + SkRect scalar_dest_rect; + scalar_dest_rect.iset(dest_rect.x(), dest_rect.y(), + dest_rect.right(), dest_rect.bottom()); + + // Transform the destination rectangle to local coordinates. + const SkMatrix& local_matrix = canvas->getTotalMatrix(); + SkRect local_dest_rect; + local_matrix.mapRect(&local_dest_rect, scalar_dest_rect); + + // After projecting the destination rectangle to local coordinates, round + // the projected rectangle to integer values, this will give us pixel values + // of the rectangle. + SkIRect local_dest_irect, local_dest_irect_saved; + local_dest_rect.round(&local_dest_irect); + local_dest_rect.round(&local_dest_irect_saved); + + // Only does the paint if the destination rect intersects with the clip + // rect. + if (local_dest_irect.intersect(canvas->getTotalClip().getBounds())) { + // At this point |local_dest_irect| contains the rect that we should draw + // to within the clipping rect. + + // Calculate the address for the top left corner of destination rect in + // the canvas that we will draw to. The address is obtained by the base + // address of the canvas shifted by "left" and "top" of the rect. + uint8* dest_rect_pointer = static_cast<uint8*>(bitmap.getPixels()) + + local_dest_irect.fTop * bitmap.rowBytes() + + local_dest_irect.fLeft * 4; + + // Project the clip rect to the original video frame, obtains the + // dimensions of the projected clip rect, "left" and "top" of the rect. + // The math here are all integer math so we won't have rounding error and + // write outside of the canvas. + // We have the assumptions of dest_rect.width() and dest_rect.height() + // being non-zero, these are valid assumptions since finding intersection + // above rejects empty rectangle so we just do a DCHECK here. + DCHECK_NE(0, dest_rect.width()); + DCHECK_NE(0, dest_rect.height()); + size_t frame_clip_width = local_dest_irect.width() * + video_frame->width() / local_dest_irect_saved.width(); + size_t frame_clip_height = local_dest_irect.height() * + video_frame->height() / local_dest_irect_saved.height(); + + // Project the "left" and "top" of the final destination rect to local + // coordinates of the video frame, use these values to find the offsets + // in the video frame to start reading. + size_t frame_clip_left = + (local_dest_irect.fLeft - local_dest_irect_saved.fLeft) * + video_frame->width() / local_dest_irect_saved.width(); + size_t frame_clip_top = + (local_dest_irect.fTop - local_dest_irect_saved.fTop) * + video_frame->height() / local_dest_irect_saved.height(); + + // Use the "left" and "top" of the destination rect to locate the offset + // in Y, U and V planes. + size_t y_offset = video_frame->stride(media::VideoFrame::kYPlane) * + frame_clip_top + frame_clip_left; + // For format YV12, there is one U, V value per 2x2 block. + // For format YV16, there is one u, V value per 2x1 block. + size_t uv_offset = (video_frame->stride(media::VideoFrame::kUPlane) * + (frame_clip_top >> y_shift)) + (frame_clip_left >> 1); + uint8* frame_clip_y = + video_frame->data(media::VideoFrame::kYPlane) + y_offset; + uint8* frame_clip_u = + video_frame->data(media::VideoFrame::kUPlane) + uv_offset; + uint8* frame_clip_v = + video_frame->data(media::VideoFrame::kVPlane) + uv_offset; + bitmap.lockPixels(); + + // TODO(hclam): do rotation and mirroring here. + // TODO(fbarchard): switch filtering based on performance. + media::ScaleYUVToRGB32(frame_clip_y, + frame_clip_u, + frame_clip_v, + dest_rect_pointer, + frame_clip_width, + frame_clip_height, + local_dest_irect.width(), + local_dest_irect.height(), + video_frame->stride(media::VideoFrame::kYPlane), + video_frame->stride(media::VideoFrame::kUPlane), + bitmap.rowBytes(), + yuv_type, + media::ROTATE_0, + media::FILTER_BILINEAR); + bitmap.unlockPixels(); + } +} + +void VideoRendererImpl::TransformToSkIRect(const SkMatrix& matrix, + const gfx::Rect& src_rect, + SkIRect* dest_rect) { + // Transform destination rect to local coordinates. + SkRect transformed_rect; + SkRect skia_dest_rect; + skia_dest_rect.iset(src_rect.x(), src_rect.y(), + src_rect.right(), src_rect.bottom()); + matrix.mapRect(&transformed_rect, skia_dest_rect); + transformed_rect.round(dest_rect); +} + +} // namespace webkit_glue diff --git a/webkit/glue/media/video_renderer_impl.h b/webkit/glue/media/video_renderer_impl.h new file mode 100644 index 0000000..30f2e38 --- /dev/null +++ b/webkit/glue/media/video_renderer_impl.h @@ -0,0 +1,122 @@ +// Copyright (c) 2010 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. +// +// The video renderer implementation to be use by the media pipeline. It lives +// inside video renderer thread and also WebKit's main thread. We need to be +// extra careful about members shared by two different threads, especially +// video frame buffers. + +#ifndef WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_ +#define WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_ + +#include "gfx/rect.h" +#include "gfx/size.h" +#include "media/base/buffers.h" +#include "media/base/filters.h" +#include "media/filters/video_renderer_base.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebMediaPlayer.h" +#include "webkit/glue/media/web_video_renderer.h" +#include "webkit/glue/webmediaplayer_impl.h" + +namespace webkit_glue { + +class VideoRendererImpl : public WebVideoRenderer { + public: + // WebVideoRenderer implementation. + virtual void SetRect(const gfx::Rect& rect); + virtual void Paint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect); + + // Static method for creating factory for this object. + static media::FilterFactory* CreateFactory(WebMediaPlayerImpl::Proxy* proxy, + bool pts_logging); + + // FilterFactoryImpl2 implementation. + static bool IsMediaFormatSupported(const media::MediaFormat& media_format); + + // TODO(scherkus): remove this mega-hack, see http://crbug.com/28207 + class FactoryFactory : public webkit_glue::WebVideoRendererFactoryFactory { + public: + FactoryFactory(bool pts_logging) + : webkit_glue::WebVideoRendererFactoryFactory(), + pts_logging_(pts_logging) { + } + + virtual media::FilterFactory* CreateFactory( + webkit_glue::WebMediaPlayerImpl::Proxy* proxy) { + return VideoRendererImpl::CreateFactory(proxy, pts_logging_); + } + + private: + // Whether we're logging video presentation timestamps (PTS). + bool pts_logging_; + + DISALLOW_COPY_AND_ASSIGN(FactoryFactory); + }; + + protected: + // Method called by VideoRendererBase during initialization. + virtual bool OnInitialize(media::VideoDecoder* decoder); + + // Method called by the VideoRendererBase when stopping. + virtual void OnStop(media::FilterCallback* callback); + + // Method called by the VideoRendererBase when a frame is available. + virtual void OnFrameAvailable(); + + private: + // Only the filter factories can create instances. + friend class media::FilterFactoryImpl2<VideoRendererImpl, + WebMediaPlayerImpl::Proxy*, + bool>; + VideoRendererImpl(WebMediaPlayerImpl::Proxy* proxy, bool pts_logging); + virtual ~VideoRendererImpl() {} + + // Determine the conditions to perform fast paint. Returns true if we can do + // fast paint otherwise false. + bool CanFastPaint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect); + + // Slow paint does a YUV => RGB, and scaled blit in two separate operations. + void SlowPaint(media::VideoFrame* video_frame, + skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect); + + // Fast paint does YUV => RGB, scaling, blitting all in one step into the + // canvas. It's not always safe and appropriate to perform fast paint. + // CanFastPaint() is used to determine the conditions. + void FastPaint(media::VideoFrame* video_frame, + skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect); + + void TransformToSkIRect(const SkMatrix& matrix, const gfx::Rect& src_rect, + SkIRect* dest_rect); + + // Pointer to our parent object that is called to request repaints. + scoped_refptr<WebMediaPlayerImpl::Proxy> proxy_; + + // An RGB bitmap used to convert the video frames. + SkBitmap bitmap_; + + // These two members are used to determine if the |bitmap_| contains + // an already converted image of the current frame. IMPORTANT NOTE: The + // value of |last_converted_frame_| must only be used for comparison purposes, + // and it should be assumed that the value of the pointer is INVALID unless + // it matches the pointer returned from GetCurrentFrame(). Even then, just + // to make sure, we compare the timestamp to be sure the bits in the + // |current_frame_bitmap_| are valid. + media::VideoFrame* last_converted_frame_; + base::TimeDelta last_converted_timestamp_; + + // The size of the video. + gfx::Size video_size_; + + // Whether we're logging video presentation timestamps (PTS). + bool pts_logging_; + + DISALLOW_COPY_AND_ASSIGN(VideoRendererImpl); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_VIDEO_RENDERER_IMPL_H_ diff --git a/webkit/glue/media/web_video_renderer.h b/webkit/glue/media/web_video_renderer.h new file mode 100644 index 0000000..8bafb1a --- /dev/null +++ b/webkit/glue/media/web_video_renderer.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_MEDIA_WEB_VIDEO_RENDERER_H_ +#define WEBKIT_GLUE_MEDIA_WEB_VIDEO_RENDERER_H_ + +#include "media/filters/video_renderer_base.h" + +namespace webkit_glue { + +// A specialized version of a VideoRenderer designed to be used inside WebKit. +class WebVideoRenderer : public media::VideoRendererBase { + public: + WebVideoRenderer() : media::VideoRendererBase() {} + virtual ~WebVideoRenderer() {} + + // This method is called with the same rect as the Paint() method and could + // be used by future implementations to implement an improved color space + + // scale code on a separate thread. Since we always do the stretch on the + // same thread as the Paint method, we just ignore the call for now. + // + // Method called on the render thread. + virtual void SetRect(const gfx::Rect& rect) = 0; + + // Paint the current front frame on the |canvas| stretching it to fit the + // |dest_rect|. + // + // Method called on the render thread. + virtual void Paint(skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(WebVideoRenderer); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_WEB_VIDEO_RENDERER_H_ diff --git a/webkit/glue/mimetype_unittest.cc b/webkit/glue/mimetype_unittest.cc new file mode 100644 index 0000000..152d20d --- /dev/null +++ b/webkit/glue/mimetype_unittest.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2006-2008 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 <string> + +#include "base/string_util.h" +#include "net/url_request/url_request_unittest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/unittest_test_server.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebFrame; + +namespace { + +class MimeTypeTests : public TestShellTest { + public: + void LoadURL(const GURL& url) { + test_shell_->LoadURL(url); + test_shell_->WaitTestFinished(); + } + + void CheckMimeType(const char* mimetype, const std::wstring& expected) { + std::string path("contenttype?"); + GURL url = server_->TestServerPage(path + mimetype); + LoadURL(url); + WebFrame* frame = test_shell_->webView()->mainFrame(); + EXPECT_EQ(expected, webkit_glue::DumpDocumentText(frame)); + } + + scoped_refptr<UnittestTestServer> server_; +}; + +TEST_F(MimeTypeTests, MimeTypeTests) { + server_ = UnittestTestServer::CreateServer(); + ASSERT_TRUE(NULL != server_.get()); + + std::wstring expected_src(L"<html>\n<body>\n" + L"<p>HTML text</p>\n</body>\n</html>\n"); + + // These files should all be displayed as plain text. + const char* plain_text[] = { + // It is unclear whether to display text/css or download it. + // Firefox 3: Display + // Internet Explorer 7: Download + // Safari 3.2: Download + // We choose to match Firefox due to the lot of complains + // from the users if css files are downloaded: + // http://code.google.com/p/chromium/issues/detail?id=7192 + "text/css", + "text/javascript", + "text/plain", + "application/x-javascript", + }; + for (size_t i = 0; i < arraysize(plain_text); ++i) { + CheckMimeType(plain_text[i], expected_src); + } + + // These should all be displayed as html content. + const char* html_src[] = { + "text/html", + "text/xml", + "text/xsl", + "application/xhtml+xml", + }; + for (size_t i = 0; i < arraysize(html_src); ++i) { + CheckMimeType(html_src[i], L"HTML text"); + } + + // These shouldn't be rendered as text or HTML, but shouldn't download + // either. + const char* not_text[] = { + "image/png", + "image/gif", + "image/jpeg", + "image/bmp", + }; + for (size_t i = 0; i < arraysize(not_text); ++i) { + CheckMimeType(not_text[i], L""); + test_shell_->webView()->mainFrame()->stopLoading(); + } + + // TODO(tc): make sure other mime types properly go to download (e.g., + // image/foo). + +} + +} // namespace diff --git a/webkit/glue/mock_resource_loader_bridge.h b/webkit/glue/mock_resource_loader_bridge.h new file mode 100644 index 0000000..7176e04 --- /dev/null +++ b/webkit/glue/mock_resource_loader_bridge.h @@ -0,0 +1,42 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ +#define WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ + +#include "base/file_path.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "webkit/glue/resource_loader_bridge.h" + +namespace webkit_glue { + +class MockResourceLoaderBridge : public webkit_glue::ResourceLoaderBridge { + public: + MockResourceLoaderBridge() { + } + + virtual ~MockResourceLoaderBridge() { + OnDestroy(); + } + + MOCK_METHOD2(AppendDataToUpload, void(const char* data, int data_len)); + MOCK_METHOD4(AppendFileRangeToUpload, + void(const FilePath& file_path, + uint64 offset, + uint64 length, + const base::Time& expected_modification_time)); + MOCK_METHOD1(SetUploadIdentifier, void(int64 identifier)); + MOCK_METHOD1(Start, bool(ResourceLoaderBridge::Peer* peer)); + MOCK_METHOD0(Cancel, void()); + MOCK_METHOD1(SetDefersLoading, void(bool value)); + MOCK_METHOD1(SyncLoad, void(SyncLoadResponse* response)); + MOCK_METHOD0(OnDestroy, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridge); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_MEDIA_MOCK_MEDIA_RESOURCE_LOADER_BRIDGE_FACTORY_H_ diff --git a/webkit/glue/multipart_response_delegate.cc b/webkit/glue/multipart_response_delegate.cc new file mode 100644 index 0000000..0b37050 --- /dev/null +++ b/webkit/glue/multipart_response_delegate.cc @@ -0,0 +1,373 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/multipart_response_delegate.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "net/base/net_util.h" +#include "net/http/http_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPHeaderVisitor.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" + +using WebKit::WebHTTPHeaderVisitor; +using WebKit::WebString; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +namespace { + +// The list of response headers that we do not copy from the original +// response when generating a WebURLResponse for a MIME payload. +const char* kReplaceHeaders[] = { + "content-type", + "content-length", + "content-disposition", + "content-range", + "range", + "set-cookie" +}; + +class HeaderCopier : public WebHTTPHeaderVisitor { + public: + HeaderCopier(WebURLResponse* response) + : response_(response) { + } + virtual void visitHeader(const WebString& name, const WebString& value) { + const std::string& name_utf8 = name.utf8(); + for (size_t i = 0; i < arraysize(kReplaceHeaders); ++i) { + if (LowerCaseEqualsASCII(name_utf8, kReplaceHeaders[i])) + return; + } + response_->setHTTPHeaderField(name, value); + } + private: + WebURLResponse* response_; +}; + +} // namespace + +MultipartResponseDelegate::MultipartResponseDelegate( + WebURLLoaderClient* client, + WebURLLoader* loader, + const WebURLResponse& response, + const std::string& boundary) + : client_(client), + loader_(loader), + original_response_(response), + boundary_("--"), + first_received_data_(true), + processing_headers_(false), + stop_sending_(false), + has_sent_first_response_(false) { + // Some servers report a boundary prefixed with "--". See bug 5786. + if (StartsWithASCII(boundary, "--", true)) { + boundary_.assign(boundary); + } else { + boundary_.append(boundary); + } +} + +void MultipartResponseDelegate::OnReceivedData(const char* data, + int data_len) { + // stop_sending_ means that we've already received the final boundary token. + // The server should stop sending us data at this point, but if it does, we + // just throw it away. + if (stop_sending_) + return; + + data_.append(data, data_len); + if (first_received_data_) { + // Some servers don't send a boundary token before the first chunk of + // data. We handle this case anyway (Gecko does too). + first_received_data_ = false; + + // Eat leading \r\n + int pos = PushOverLine(data_, 0); + if (pos) + data_ = data_.substr(pos); + + if (data_.length() < boundary_.length() + 2) { + // We don't have enough data yet to make a boundary token. Just wait + // until the next chunk of data arrives. + first_received_data_ = true; + return; + } + + if (0 != data_.compare(0, boundary_.length(), boundary_)) { + data_ = boundary_ + "\n" + data_; + } + } + DCHECK(!first_received_data_); + + // Headers + if (processing_headers_) { + // Eat leading \r\n + int pos = PushOverLine(data_, 0); + if (pos) + data_ = data_.substr(pos); + + if (ParseHeaders()) { + // Successfully parsed headers. + processing_headers_ = false; + } else { + // Get more data before trying again. + return; + } + } + DCHECK(!processing_headers_); + + size_t boundary_pos; + while ((boundary_pos = FindBoundary()) != std::string::npos) { + if (boundary_pos > 0 && client_) { + // Strip out trailing \n\r characters in the buffer preceding the + // boundary on the same lines as Firefox. + size_t data_length = boundary_pos; + if (data_[boundary_pos - 1] == '\n') { + data_length--; + if (data_[boundary_pos - 2] == '\r') { + data_length--; + } + } + if (data_length > 0) { + // Send the last data chunk. + client_->didReceiveData(loader_, + data_.data(), + static_cast<int>(data_length)); + } + } + size_t boundary_end_pos = boundary_pos + boundary_.length(); + if (boundary_end_pos < data_.length() && '-' == data_[boundary_end_pos]) { + // This was the last boundary so we can stop processing. + stop_sending_ = true; + data_.clear(); + return; + } + + // We can now throw out data up through the boundary + int offset = PushOverLine(data_, boundary_end_pos); + data_ = data_.substr(boundary_end_pos + offset); + + // Ok, back to parsing headers + if (!ParseHeaders()) { + processing_headers_ = true; + break; + } + } + + // At this point, we should send over any data we have, but keep enough data + // buffered to handle a boundary that may have been truncated. + if (!processing_headers_ && data_.length() > boundary_.length()) { + // If the last character is a new line character, go ahead and just send + // everything we have buffered. This matches an optimization in Gecko. + int send_length = data_.length() - boundary_.length(); + if (data_[data_.length() - 1] == '\n') + send_length = data_.length(); + if (client_) + client_->didReceiveData(loader_, data_.data(), send_length); + data_ = data_.substr(send_length); + } +} + +void MultipartResponseDelegate::OnCompletedRequest() { + // If we have any pending data and we're not in a header, go ahead and send + // it to WebCore. + if (!processing_headers_ && !data_.empty() && !stop_sending_ && client_) { + client_->didReceiveData(loader_, + data_.data(), + static_cast<int>(data_.length())); + } +} + +int MultipartResponseDelegate::PushOverLine(const std::string& data, + size_t pos) { + int offset = 0; + if (pos < data.length() && (data[pos] == '\r' || data[pos] == '\n')) { + ++offset; + if (pos + 1 < data.length() && data[pos + 1] == '\n') + ++offset; + } + return offset; +} + +bool MultipartResponseDelegate::ParseHeaders() { + int line_feed_increment = 1; + + // Grab the headers being liberal about line endings. + size_t line_start_pos = 0; + size_t line_end_pos = data_.find('\n'); + while (line_end_pos != std::string::npos) { + // Handle CRLF + if (line_end_pos > line_start_pos && data_[line_end_pos - 1] == '\r') { + line_feed_increment = 2; + --line_end_pos; + } else { + line_feed_increment = 1; + } + if (line_start_pos == line_end_pos) { + // A blank line, end of headers + line_end_pos += line_feed_increment; + break; + } + // Find the next header line. + line_start_pos = line_end_pos + line_feed_increment; + line_end_pos = data_.find('\n', line_start_pos); + } + // Truncated in the middle of a header, stop parsing. + if (line_end_pos == std::string::npos) + return false; + + // Eat headers + std::string headers("\n"); + headers.append(data_, 0, line_end_pos); + data_ = data_.substr(line_end_pos); + + // Create a WebURLResponse based on the original set of headers + the + // replacement headers. We only replace the same few headers that gecko + // does. See netwerk/streamconv/converters/nsMultiMixedConv.cpp. + std::string content_type = net::GetSpecificHeader(headers, "content-type"); + std::string mime_type; + std::string charset; + bool has_charset = false; + net::HttpUtil::ParseContentType(content_type, &mime_type, &charset, + &has_charset); + WebURLResponse response(original_response_.url()); + response.setMIMEType(WebString::fromUTF8(mime_type)); + response.setTextEncodingName(WebString::fromUTF8(charset)); + + HeaderCopier copier(&response); + original_response_.visitHTTPHeaderFields(&copier); + + for (size_t i = 0; i < arraysize(kReplaceHeaders); ++i) { + std::string name(kReplaceHeaders[i]); + std::string value = net::GetSpecificHeader(headers, name); + if (!value.empty()) { + response.setHTTPHeaderField(WebString::fromUTF8(name), + WebString::fromUTF8(value)); + } + } + // To avoid recording every multipart load as a separate visit in + // the history database, we want to keep track of whether the response + // is part of a multipart payload. We do want to record the first visit, + // so we only set isMultipartPayload to true after the first visit. + response.setIsMultipartPayload(has_sent_first_response_); + has_sent_first_response_ = true; + // Send the response! + if (client_) + client_->didReceiveResponse(loader_, response); + + return true; +} + +// Boundaries are supposed to be preceeded with --, but it looks like gecko +// doesn't require the dashes to exist. See nsMultiMixedConv::FindToken. +size_t MultipartResponseDelegate::FindBoundary() { + size_t boundary_pos = data_.find(boundary_); + if (boundary_pos != std::string::npos) { + // Back up over -- for backwards compat + // TODO(tc): Don't we only want to do this once? Gecko code doesn't seem + // to care. + if (boundary_pos >= 2) { + if ('-' == data_[boundary_pos - 1] && '-' == data_[boundary_pos - 2]) { + boundary_pos -= 2; + boundary_ = "--" + boundary_; + } + } + } + return boundary_pos; +} + +bool MultipartResponseDelegate::ReadMultipartBoundary( + const WebURLResponse& response, + std::string* multipart_boundary) { + std::string content_type = + response.httpHeaderField(WebString::fromUTF8("Content-Type")).utf8(); + + size_t boundary_start_offset = content_type.find("boundary="); + if (boundary_start_offset == std::wstring::npos) { + return false; + } + + boundary_start_offset += strlen("boundary="); + + size_t boundary_end_offset = content_type.find(';', boundary_start_offset); + + if (boundary_end_offset == std::string::npos) + boundary_end_offset = content_type.length(); + + size_t boundary_length = boundary_end_offset - boundary_start_offset; + + *multipart_boundary = + content_type.substr(boundary_start_offset, boundary_length); + // The byte range response can have quoted boundary strings. This is legal + // as per MIME specifications. Individual data fragements however don't + // contain quoted boundary strings. + TrimString(*multipart_boundary, "\"", multipart_boundary); + return true; +} + +bool MultipartResponseDelegate::ReadContentRanges( + const WebURLResponse& response, + int* content_range_lower_bound, + int* content_range_upper_bound) { + + std::string content_range = response.httpHeaderField("Content-Range").utf8(); + if (content_range.empty()) { + content_range = response.httpHeaderField("Range").utf8(); + } + + if (content_range.empty()) { + DLOG(WARNING) << "Failed to read content range from response."; + return false; + } + + size_t byte_range_lower_bound_start_offset = content_range.find(" "); + if (byte_range_lower_bound_start_offset == std::string::npos) { + return false; + } + + // Skip over the initial space. + byte_range_lower_bound_start_offset++; + + size_t byte_range_lower_bound_end_offset = + content_range.find("-", byte_range_lower_bound_start_offset); + if (byte_range_lower_bound_end_offset == std::string::npos) { + return false; + } + + size_t byte_range_lower_bound_characters = + byte_range_lower_bound_end_offset - byte_range_lower_bound_start_offset; + std::string byte_range_lower_bound = + content_range.substr(byte_range_lower_bound_start_offset, + byte_range_lower_bound_characters); + + size_t byte_range_upper_bound_start_offset = + byte_range_lower_bound_end_offset + 1; + + size_t byte_range_upper_bound_end_offset = + content_range.find("/", byte_range_upper_bound_start_offset); + if (byte_range_upper_bound_end_offset == std::string::npos) { + return false; + } + + size_t byte_range_upper_bound_characters = + byte_range_upper_bound_end_offset - byte_range_upper_bound_start_offset; + + std::string byte_range_upper_bound = + content_range.substr(byte_range_upper_bound_start_offset, + byte_range_upper_bound_characters); + + if (!StringToInt(byte_range_lower_bound, content_range_lower_bound)) + return false; + if (!StringToInt(byte_range_upper_bound, content_range_upper_bound)) + return false; + return true; +} + +} // namespace webkit_glue diff --git a/webkit/glue/multipart_response_delegate.h b/webkit/glue/multipart_response_delegate.h new file mode 100644 index 0000000..aded54a --- /dev/null +++ b/webkit/glue/multipart_response_delegate.h @@ -0,0 +1,147 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A delegate class of WebURLLoaderImpl that handles multipart/x-mixed-replace +// data. We special case multipart/x-mixed-replace because WebCore expects a +// separate didReceiveResponse for each new message part. +// +// Most of the logic and edge case handling are based on the Mozilla's +// implementation in netwerk/streamconv/converters/nsMultiMixedConv.cpp. +// This seems like a derivative work, so here's the original license: + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef WEBKIT_GLUE_MULTIPART_RESPONSE_DELEGATE_H_ +#define WEBKIT_GLUE_MULTIPART_RESPONSE_DELEGATE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +namespace WebKit { +class WebURLLoader; +class WebURLLoaderClient; +} + +namespace webkit_glue { + +// Used by unit tests to access private members. +class MultipartResponseDelegateTester; + +class MultipartResponseDelegate { + public: + MultipartResponseDelegate(WebKit::WebURLLoaderClient* client, + WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response, + const std::string& boundary); + + // Passed through from ResourceHandleInternal + void OnReceivedData(const char* data, int data_len); + void OnCompletedRequest(); + + // The request has been canceled, so stop making calls to the client. + void Cancel() { + client_ = NULL; + loader_ = NULL; + } + + // Returns the multi part boundary string from the Content-type header + // in the response. + // Returns true on success. + static bool ReadMultipartBoundary(const WebKit::WebURLResponse& response, + std::string* multipart_boundary); + + // Returns the lower and higher content ranges from an individual multipart + // in a multipart response. + // Returns true on success. + static bool ReadContentRanges(const WebKit::WebURLResponse& response, + int* content_range_lower_bound, + int* content_range_upper_bound); + + private: + friend class MultipartResponseDelegateTester; // For unittests. + + // Pointers to the client and associated loader so we can make callbacks as + // we parse pieces of data. + WebKit::WebURLLoaderClient* client_; + WebKit::WebURLLoader* loader_; + + // The original resource response for this request. We use this as a + // starting point for each parts response. + WebKit::WebURLResponse original_response_; + + // Checks to see if data[pos] character is a line break; handles crlf, lflf, + // lf, or cr. Returns the number of characters to skip over (0, 1 or 2). + int PushOverLine(const std::string& data, size_t pos); + + // Tries to parse http headers from the start of data_. Returns true if it + // succeeds and sends a didReceiveResponse to m_client. Returns false if + // the header is incomplete (in which case we just wait for more data). + bool ParseHeaders(); + + // Find the next boundary in data_. Returns std::string::npos if there's no + // full token. + size_t FindBoundary(); + + // A temporary buffer to hold data between reads for multipart data that + // gets split in the middle of a header. + std::string data_; + + // Multipart boundary token + std::string boundary_; + + // true until we get our first on received data call + bool first_received_data_; + + // true if we're truncated in the middle of a header + bool processing_headers_; + + // true when we're done sending information. At that point, we stop + // processing AddData requests. + bool stop_sending_; + + // true after we've sent our first response to the WebURLLoaderClient. + bool has_sent_first_response_; + + DISALLOW_COPY_AND_ASSIGN(MultipartResponseDelegate); +}; + +} // namespace webkit_glue + +#endif diff --git a/webkit/glue/multipart_response_delegate_unittest.cc b/webkit/glue/multipart_response_delegate_unittest.cc new file mode 100644 index 0000000..0433b52 --- /dev/null +++ b/webkit/glue/multipart_response_delegate_unittest.cc @@ -0,0 +1,638 @@ +// Copyright (c) 2006-2008 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 <vector> + +#include "base/basictypes.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "webkit/glue/multipart_response_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using webkit_glue::MultipartResponseDelegate; +using webkit_glue::MultipartResponseDelegateTester; + +namespace webkit_glue { + +class MultipartResponseDelegateTester { + public: + MultipartResponseDelegateTester(MultipartResponseDelegate* delegate) + : delegate_(delegate) { + } + + int PushOverLine(const std::string& data, size_t pos) { + return delegate_->PushOverLine(data, pos); + } + + bool ParseHeaders() { return delegate_->ParseHeaders(); } + size_t FindBoundary() { return delegate_->FindBoundary(); } + std::string& boundary() { return delegate_->boundary_; } + std::string& data() { return delegate_->data_; } + + private: + MultipartResponseDelegate* delegate_; +}; + +} // namespace webkit_glue + +namespace { + +class MultipartResponseTest : public testing::Test { +}; + +class MockWebURLLoaderClient : public WebURLLoaderClient { + public: + MockWebURLLoaderClient() { Reset(); } + + virtual void willSendRequest( + WebURLLoader*, WebURLRequest&, const WebURLResponse&) {} + virtual void didSendData( + WebURLLoader*, unsigned long long, unsigned long long) {} + + virtual void didReceiveResponse(WebURLLoader* loader, + const WebURLResponse& response) { + ++received_response_; + response_ = response; + data_.clear(); + } + virtual void didReceiveData(WebURLLoader* loader, + const char* data, int data_length) { + ++received_data_; + data_.append(data, data_length); + } + + virtual void didFinishLoading(WebURLLoader*) {} + virtual void didFail(WebURLLoader*, const WebURLError&) {} + + void Reset() { + received_response_ = received_data_ = 0; + data_.clear(); + response_.reset(); + } + + string GetResponseHeader(const char* name) const { + return string(response_.httpHeaderField(WebString::fromUTF8(name)).utf8()); + } + + int received_response_, received_data_; + string data_; + WebURLResponse response_; +}; + +// We can't put this in an anonymous function because it's a friend class for +// access to private members. +TEST(MultipartResponseTest, Functions) { + // PushOverLine tests + + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + response.setHTTPHeaderField(WebString::fromUTF8("Foo"), + WebString::fromUTF8("Bar")); + response.setHTTPHeaderField(WebString::fromUTF8("Content-type"), + WebString::fromUTF8("text/plain")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "bound"); + MultipartResponseDelegateTester delegate_tester(&delegate); + + struct { + const char* input; + const int position; + const int expected; + } line_tests[] = { + { "Line", 0, 0 }, + { "Line", 2, 0 }, + { "Line", 10, 0 }, + { "\r\nLine", 0, 2 }, + { "\nLine", 0, 1 }, + { "\n\nLine", 0, 2 }, + { "\rLine", 0, 1 }, + { "Line\r\nLine", 4, 2 }, + { "Line\nLine", 4, 1 }, + { "Line\n\nLine", 4, 2 }, + { "Line\rLine", 4, 1 }, + { "Line\r\rLine", 4, 1 }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(line_tests); ++i) { + EXPECT_EQ(line_tests[i].expected, + delegate_tester.PushOverLine(line_tests[i].input, + line_tests[i].position)); + } + + // ParseHeaders tests + struct { + const char* data; + const bool rv; + const int received_response_calls; + const char* newdata; + } header_tests[] = { + { "This is junk", false, 0, "This is junk" }, + { "Foo: bar\nBaz:\n\nAfter:\n", true, 1, "After:\n" }, + { "Foo: bar\nBaz:\n", false, 0, "Foo: bar\nBaz:\n" }, + { "Foo: bar\r\nBaz:\r\n\r\nAfter:\r\n", true, 1, "After:\r\n" }, + { "Foo: bar\r\nBaz:\r\n", false, 0, "Foo: bar\r\nBaz:\r\n" }, + { "Foo: bar\nBaz:\r\n\r\nAfter:\n\n", true, 1, "After:\n\n" }, + { "Foo: bar\r\nBaz:\n", false, 0, "Foo: bar\r\nBaz:\n" }, + { "\r\n", true, 1, "" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(header_tests); ++i) { + client.Reset(); + delegate_tester.data().assign(header_tests[i].data); + EXPECT_EQ(header_tests[i].rv, + delegate_tester.ParseHeaders()); + EXPECT_EQ(header_tests[i].received_response_calls, + client.received_response_); + EXPECT_EQ(string(header_tests[i].newdata), + delegate_tester.data()); + } + // Test that the resource response is filled in correctly when parsing + // headers. + client.Reset(); + string test_header("content-type: image/png\ncontent-length: 10\n\n"); + delegate_tester.data().assign(test_header); + EXPECT_TRUE(delegate_tester.ParseHeaders()); + EXPECT_TRUE(delegate_tester.data().length() == 0); + EXPECT_EQ(string("image/png"), client.GetResponseHeader("Content-Type")); + EXPECT_EQ(string("10"), client.GetResponseHeader("content-length")); + // This header is passed from the original request. + EXPECT_EQ(string("Bar"), client.GetResponseHeader("foo")); + + // Make sure we parse the right mime-type if a charset is provided. + client.Reset(); + string test_header2("content-type: text/html; charset=utf-8\n\n"); + delegate_tester.data().assign(test_header2); + EXPECT_TRUE(delegate_tester.ParseHeaders()); + EXPECT_TRUE(delegate_tester.data().length() == 0); + EXPECT_EQ(string("text/html; charset=utf-8"), + client.GetResponseHeader("Content-Type")); + EXPECT_EQ(string("utf-8"), + string(client.response_.textEncodingName().utf8())); + + // FindBoundary tests + struct { + const char* boundary; + const char* data; + const size_t position; + } boundary_tests[] = { + { "bound", "bound", 0 }, + { "bound", "--bound", 0 }, + { "bound", "junkbound", 4 }, + { "bound", "junk--bound", 4 }, + { "foo", "bound", string::npos }, + { "bound", "--boundbound", 0 }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(boundary_tests); ++i) { + delegate_tester.boundary().assign(boundary_tests[i].boundary); + delegate_tester.data().assign(boundary_tests[i].data); + EXPECT_EQ(boundary_tests[i].position, + delegate_tester.FindBoundary()); + } +} + +TEST(MultipartResponseTest, MissingBoundaries) { + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + response.setHTTPHeaderField(WebString::fromUTF8("Foo"), + WebString::fromUTF8("Bar")); + response.setHTTPHeaderField(WebString::fromUTF8("Content-type"), + WebString::fromUTF8("text/plain")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "bound"); + + // No start boundary + string no_start_boundary( + "Content-type: text/plain\n\n" + "This is a sample response\n" + "--bound--" + "ignore junk after end token --bound\n\nTest2\n"); + delegate.OnReceivedData(no_start_boundary.c_str(), + static_cast<int>(no_start_boundary.length())); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ(string("This is a sample response"), + client.data_); + + delegate.OnCompletedRequest(); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + + // No end boundary + client.Reset(); + MultipartResponseDelegate delegate2(&client, NULL, response, "bound"); + string no_end_boundary( + "bound\nContent-type: text/plain\n\n" + "This is a sample response\n"); + delegate2.OnReceivedData(no_end_boundary.c_str(), + static_cast<int>(no_end_boundary.length())); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ("This is a sample response\n", client.data_); + + delegate2.OnCompletedRequest(); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ(string("This is a sample response\n"), + client.data_); + + // Neither boundary + client.Reset(); + MultipartResponseDelegate delegate3(&client, NULL, response, "bound"); + string no_boundaries( + "Content-type: text/plain\n\n" + "This is a sample response\n"); + delegate3.OnReceivedData(no_boundaries.c_str(), + static_cast<int>(no_boundaries.length())); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ("This is a sample response\n", client.data_); + + delegate3.OnCompletedRequest(); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ(string("This is a sample response\n"), + client.data_); +} + +TEST(MultipartResponseTest, MalformedBoundary) { + // Some servers send a boundary that is prefixed by "--". See bug 5786. + + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + response.setHTTPHeaderField(WebString::fromUTF8("Foo"), + WebString::fromUTF8("Bar")); + response.setHTTPHeaderField(WebString::fromUTF8("Content-type"), + WebString::fromUTF8("text/plain")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "--bound"); + + string data( + "--bound\n" + "Content-type: text/plain\n\n" + "This is a sample response\n" + "--bound--" + "ignore junk after end token --bound\n\nTest2\n"); + delegate.OnReceivedData(data.c_str(), static_cast<int>(data.length())); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); + EXPECT_EQ(string("This is a sample response"), client.data_); + + delegate.OnCompletedRequest(); + EXPECT_EQ(1, client.received_response_); + EXPECT_EQ(1, client.received_data_); +} + + +// Used in for tests that break the data in various places. +struct TestChunk { + const int start_pos; // offset in data + const int end_pos; // end offset in data + const int expected_responses; + const int expected_received_data; + const char* expected_data; +}; + +void VariousChunkSizesTest(const TestChunk chunks[], int chunks_size, + int responses, int received_data, + const char* completed_data) { + const string data( + "--bound\n" // 0-7 + "Content-type: image/png\n\n" // 8-32 + "datadatadatadatadata" // 33-52 + "--bound\n" // 53-60 + "Content-type: image/jpg\n\n" // 61-85 + "foofoofoofoofoo" // 86-100 + "--bound--"); // 101-109 + + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "bound"); + + for (int i = 0; i < chunks_size; ++i) { + ASSERT_TRUE(chunks[i].start_pos < chunks[i].end_pos); + string chunk = data.substr(chunks[i].start_pos, + chunks[i].end_pos - chunks[i].start_pos); + delegate.OnReceivedData(chunk.c_str(), static_cast<int>(chunk.length())); + EXPECT_EQ(chunks[i].expected_responses, + client.received_response_); + EXPECT_EQ(chunks[i].expected_received_data, + client.received_data_); + EXPECT_EQ(string(chunks[i].expected_data), + client.data_); + } + // Check final state + delegate.OnCompletedRequest(); + EXPECT_EQ(responses, + client.received_response_); + EXPECT_EQ(received_data, + client.received_data_); + EXPECT_EQ(string(completed_data), + client.data_); +} + +TEST(MultipartResponseTest, BreakInBoundary) { + // Break in the first boundary + const TestChunk bound1[] = { + { 0, 4, 0, 0, ""}, + { 4, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(bound1, arraysize(bound1), + 2, 2, "foofoofoofoofoo"); + + // Break in first and second + const TestChunk bound2[] = { + { 0, 4, 0, 0, ""}, + { 4, 55, 1, 1, "datadatadatadat" }, + { 55, 65, 1, 2, "datadatadatadatadata" }, + { 65, 110, 2, 3, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(bound2, arraysize(bound2), + 2, 3, "foofoofoofoofoo"); + + // Break in second only + const TestChunk bound3[] = { + { 0, 55, 1, 1, "datadatadatadat" }, + { 55, 110, 2, 3, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(bound3, arraysize(bound3), + 2, 3, "foofoofoofoofoo"); +} + +TEST(MultipartResponseTest, BreakInHeaders) { + // Break in first header + const TestChunk header1[] = { + { 0, 10, 0, 0, "" }, + { 10, 35, 1, 0, "" }, + { 35, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(header1, arraysize(header1), + 2, 2, "foofoofoofoofoo"); + + // Break in both headers + const TestChunk header2[] = { + { 0, 10, 0, 0, "" }, + { 10, 65, 1, 1, "datadatadatadatadata" }, + { 65, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(header2, arraysize(header2), + 2, 2, "foofoofoofoofoo"); + + // Break at end of a header + const TestChunk header3[] = { + { 0, 33, 1, 0, "" }, + { 33, 65, 1, 1, "datadatadatadatadata" }, + { 65, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(header3, arraysize(header3), + 2, 2, "foofoofoofoofoo"); +} + +TEST(MultipartResponseTest, BreakInData) { + // All data as one chunk + const TestChunk data1[] = { + { 0, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(data1, arraysize(data1), + 2, 2, "foofoofoofoofoo"); + + // breaks in data segment + const TestChunk data2[] = { + { 0, 35, 1, 0, "" }, + { 35, 65, 1, 1, "datadatadatadatadata" }, + { 65, 90, 2, 1, "" }, + { 90, 110, 2, 2, "foofoofoofoofoo" }, + }; + VariousChunkSizesTest(data2, arraysize(data2), + 2, 2, "foofoofoofoofoo"); + + // Incomplete send + const TestChunk data3[] = { + { 0, 35, 1, 0, "" }, + { 35, 90, 2, 1, "" }, + }; + VariousChunkSizesTest(data3, arraysize(data3), + 2, 2, "foof"); +} + +TEST(MultipartResponseTest, MultipleBoundaries) { + // Test multiple boundaries back to back + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "bound"); + + string data("--bound\r\n\r\n--bound\r\n\r\nfoofoo--bound--"); + delegate.OnReceivedData(data.c_str(), static_cast<int>(data.length())); + EXPECT_EQ(2, + client.received_response_); + EXPECT_EQ(1, + client.received_data_); + EXPECT_EQ(string("foofoo"), + client.data_); +} + +TEST(MultipartResponseTest, MultipartByteRangeParsingTest) { + // Test multipart/byteranges based boundary parsing. + WebURLResponse response1; + response1.initialize(); + response1.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + response1.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("200")); + response1.setHTTPHeaderField( + WebString::fromUTF8("Content-type"), + WebString::fromUTF8("multipart/byteranges; boundary=--bound--")); + + std::string multipart_boundary; + bool result = MultipartResponseDelegate::ReadMultipartBoundary( + response1, &multipart_boundary); + EXPECT_EQ(result, true); + EXPECT_EQ(string("--bound--"), + multipart_boundary); + + WebURLResponse response2; + response2.initialize(); + response2.setMIMEType(WebString::fromUTF8("image/png")); + + response2.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("300")); + response2.setHTTPHeaderField( + WebString::fromUTF8("Last-Modified"), + WebString::fromUTF8("Mon, 04 Apr 2005 20:36:01 GMT")); + response2.setHTTPHeaderField( + WebString::fromUTF8("Date"), + WebString::fromUTF8("Thu, 11 Sep 2008 18:21:42 GMT")); + + multipart_boundary.clear(); + result = MultipartResponseDelegate::ReadMultipartBoundary( + response2, &multipart_boundary); + EXPECT_EQ(result, false); + + WebURLResponse response3; + response3.initialize(); + response3.setMIMEType(WebString::fromUTF8("multipart/byteranges")); + + response3.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("300")); + response3.setHTTPHeaderField( + WebString::fromUTF8("Last-Modified"), + WebString::fromUTF8("Mon, 04 Apr 2005 20:36:01 GMT")); + response3.setHTTPHeaderField( + WebString::fromUTF8("Date"), + WebString::fromUTF8("Thu, 11 Sep 2008 18:21:42 GMT")); + response3.setHTTPHeaderField( + WebString::fromUTF8("Content-type"), + WebString::fromUTF8("multipart/byteranges")); + + multipart_boundary.clear(); + result = MultipartResponseDelegate::ReadMultipartBoundary( + response3, &multipart_boundary); + EXPECT_EQ(result, false); + EXPECT_EQ(multipart_boundary.length(), 0U); + + WebURLResponse response4; + response4.initialize(); + response4.setMIMEType(WebString::fromUTF8("multipart/byteranges")); + response4.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("200")); + response4.setHTTPHeaderField( + WebString::fromUTF8("Content-type"), + WebString::fromUTF8( + "multipart/byteranges; boundary=--bound--; charSet=utf8")); + + multipart_boundary.clear(); + + result = MultipartResponseDelegate::ReadMultipartBoundary( + response4, &multipart_boundary); + EXPECT_EQ(result, true); + EXPECT_EQ(string("--bound--"), multipart_boundary); + + WebURLResponse response5; + response5.initialize(); + response5.setMIMEType(WebString::fromUTF8("multipart/byteranges")); + response5.setHTTPHeaderField(WebString::fromUTF8("Content-Length"), + WebString::fromUTF8("200")); + response5.setHTTPHeaderField( + WebString::fromUTF8("Content-type"), + WebString::fromUTF8( + "multipart/byteranges; boundary=\"--bound--\"; charSet=utf8")); + + multipart_boundary.clear(); + + result = MultipartResponseDelegate::ReadMultipartBoundary( + response5, &multipart_boundary); + EXPECT_EQ(result, true); + EXPECT_EQ(string("--bound--"), multipart_boundary); +} + +TEST(MultipartResponseTest, MultipartContentRangesTest) { + WebURLResponse response1; + response1.initialize(); + response1.setMIMEType("application/pdf"); + response1.setHTTPHeaderField("Content-Length", "200"); + response1.setHTTPHeaderField("Content-Range", "bytes 1000-1050/5000"); + + int content_range_lower_bound = 0; + int content_range_upper_bound = 0; + + bool result = MultipartResponseDelegate::ReadContentRanges( + response1, &content_range_lower_bound, + &content_range_upper_bound); + + EXPECT_EQ(result, true); + EXPECT_EQ(content_range_lower_bound, 1000); + EXPECT_EQ(content_range_upper_bound, 1050); + + WebURLResponse response2; + response2.initialize(); + response2.setMIMEType("application/pdf"); + response2.setHTTPHeaderField("Content-Length", "200"); + response2.setHTTPHeaderField("Content-Range", "bytes 1000/1050"); + + content_range_lower_bound = 0; + content_range_upper_bound = 0; + + result = MultipartResponseDelegate::ReadContentRanges( + response2, &content_range_lower_bound, + &content_range_upper_bound); + + EXPECT_EQ(result, false); + + WebURLResponse response3; + response3.initialize(); + response3.setMIMEType("application/pdf"); + response3.setHTTPHeaderField("Content-Length", "200"); + response3.setHTTPHeaderField("Range", "bytes 1000-1050/5000"); + + content_range_lower_bound = 0; + content_range_upper_bound = 0; + + result = MultipartResponseDelegate::ReadContentRanges( + response3, &content_range_lower_bound, + &content_range_upper_bound); + + EXPECT_EQ(result, true); + EXPECT_EQ(content_range_lower_bound, 1000); + EXPECT_EQ(content_range_upper_bound, 1050); + + WebURLResponse response4; + response4.initialize(); + response4.setMIMEType("application/pdf"); + response4.setHTTPHeaderField("Content-Length", "200"); + + content_range_lower_bound = 0; + content_range_upper_bound = 0; + + result = MultipartResponseDelegate::ReadContentRanges( + response4, &content_range_lower_bound, + &content_range_upper_bound); + + EXPECT_EQ(result, false); +} + +TEST(MultipartResponseTest, MultipartPayloadSet) { + WebURLResponse response; + response.initialize(); + response.setMIMEType(WebString::fromUTF8("multipart/x-mixed-replace")); + MockWebURLLoaderClient client; + MultipartResponseDelegate delegate(&client, NULL, response, "bound"); + + string data( + "--bound\n" + "Content-type: text/plain\n\n" + "response data\n" + "--bound\n"); + delegate.OnReceivedData(data.c_str(), static_cast<int>(data.length())); + EXPECT_EQ(1, + client.received_response_); + EXPECT_EQ(string("response data"), + client.data_); + EXPECT_EQ(false, client.response_.isMultipartPayload()); + + string data2( + "Content-type: text/plain\n\n" + "response data2\n" + "--bound\n"); + delegate.OnReceivedData(data2.c_str(), static_cast<int>(data2.length())); + EXPECT_EQ(2, + client.received_response_); + EXPECT_EQ(string("response data2"), + client.data_); + EXPECT_EQ(true, client.response_.isMultipartPayload()); +} + +} // namespace diff --git a/webkit/glue/npruntime_util.cc b/webkit/glue/npruntime_util.cc new file mode 100644 index 0000000..dfc3a2b --- /dev/null +++ b/webkit/glue/npruntime_util.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/npruntime_util.h" + +#include "base/pickle.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" + +using WebKit::WebBindings; + +namespace webkit_glue { + +bool SerializeNPIdentifier(NPIdentifier identifier, Pickle* pickle) { + const NPUTF8* string; + int32_t number; + bool is_string; + WebBindings::extractIdentifierData(identifier, string, number, is_string); + + if (!pickle->WriteBool(is_string)) + return false; + if (is_string) { + // Write the null byte for efficiency on the other end. + return pickle->WriteData(string, strlen(string) + 1); + } + return pickle->WriteInt(number); +} + +bool DeserializeNPIdentifier(const Pickle& pickle, void** pickle_iter, + NPIdentifier* identifier) { + bool is_string; + if (!pickle.ReadBool(pickle_iter, &is_string)) + return false; + + if (is_string) { + const char* data; + int data_len; + if (!pickle.ReadData(pickle_iter, &data, &data_len)) + return false; + DCHECK_EQ((static_cast<size_t>(data_len)), strlen(data) + 1); + *identifier = WebBindings::getStringIdentifier(data); + } else { + int number; + if (!pickle.ReadInt(pickle_iter, &number)) + return false; + *identifier = WebBindings::getIntIdentifier(number); + } + return true; +} + +} // namespace webkit_glue diff --git a/webkit/glue/npruntime_util.h b/webkit/glue/npruntime_util.h new file mode 100644 index 0000000..1135246 --- /dev/null +++ b/webkit/glue/npruntime_util.h @@ -0,0 +1,21 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_NPRUNTIME_UTIL_H_ +#define WEBKIT_GLUE_NPRUNTIME_UTIL_H_ + +#include "third_party/npapi/bindings/npruntime.h" + +class Pickle; + +namespace webkit_glue { + +// Efficiently serialize/deserialize a NPIdentifier +bool SerializeNPIdentifier(NPIdentifier identifier, Pickle* pickle); +bool DeserializeNPIdentifier(const Pickle& pickle, void** pickle_iter, + NPIdentifier* identifier); + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_NPRUNTIME_UTIL_H_ diff --git a/webkit/glue/password_form.h b/webkit/glue/password_form.h new file mode 100644 index 0000000..57a54c1 --- /dev/null +++ b/webkit/glue/password_form.h @@ -0,0 +1,168 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PASSWORD_FORM_H__ +#define WEBKIT_GLUE_PASSWORD_FORM_H__ + +#include <string> +#include <map> + +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPasswordFormData.h" + +namespace webkit_glue { + +// The PasswordForm struct encapsulates information about a login form, +// which can be an HTML form or a dialog with username/password text fields. +// +// The Web Data database stores saved username/passwords and associated form +// metdata using a PasswordForm struct, typically one that was created from +// a parsed HTMLFormElement or LoginDialog, but the saved entries could have +// also been created by imported data from another browser. +// +// The PasswordManager implements a fuzzy-matching algorithm to compare saved +// PasswordForm entries against PasswordForms that were created from a parsed +// HTML or dialog form. As one might expect, the more data contained in one +// of the saved PasswordForms, the better the job the PasswordManager can do +// in matching it against the actual form it was saved on, and autofill +// accurately. But it is not always possible, especially when importing from +// other browsers with different data models, to copy over all the information +// about a particular "saved password entry" to our PasswordForm +// representation. +// +// The field descriptions in the struct specification below are +// intended to describe which fields are not strictly required when adding a saved +// password entry to the database and how they can affect the matching process. + +struct PasswordForm { + // Enum to differentiate between HTML form based authentication, and dialogs + // using basic or digest schemes. Default is SCHEME_HTML. Only PasswordForms + // of the same Scheme will be matched/autofilled against each other. + enum Scheme { + SCHEME_HTML, + SCHEME_BASIC, + SCHEME_DIGEST, + SCHEME_OTHER + } scheme; + + // The "Realm" for the sign-on (scheme, host, port for SCHEME_HTML, and + // contains the HTTP realm for dialog-based forms). + // The signon_realm is effectively the primary key used for retrieving + // data from the database, so it must not be empty. + std::string signon_realm; + + // The URL (minus query parameters) containing the form. This is the primary + // data used by the PasswordManager to decide (in longest matching prefix + // fashion) whether or not a given PasswordForm result from the database is a + // good fit for a particular form on a page, so it must not be empty. + GURL origin; + + // The action target of the form. This is the primary data used by the + // PasswordManager for form autofill; that is, the action of the saved + // credentials must match the action of the form on the page to be autofilled. + // If this is empty / not available, it will result in a "restricted" + // IE-like autofill policy, where we wait for the user to type in his + // username before autofilling the password. In these cases, after successful + // login the action URL will automatically be assigned by the + // PasswordManager. + // + // When parsing an HTML form, this must always be set. + GURL action; + + // The name of the submit button used. Optional; only used in scoring + // of PasswordForm results from the database to make matches as tight as + // possible. + // + // When parsing an HTML form, this must always be set. + string16 submit_element; + + // The name of the username input element. Optional (improves scoring). + // + // When parsing an HTML form, this must always be set. + string16 username_element; + + // The username. Optional. + // + // When parsing an HTML form, this is typically empty unless the site + // has implemented some form of autofill. + string16 username_value; + + // The name of the password input element, Optional (improves scoring). + // + // When parsing an HTML form, this must always be set. + string16 password_element; + + // The password. Required. + // + // When parsing an HTML form, this is typically empty. + string16 password_value; + + // If the form was a change password form, the name of the + // 'old password' input element. Optional. + string16 old_password_element; + + // The old password. Optional. + string16 old_password_value; + + // Whether or not this login was saved under an HTTPS session with a valid + // SSL cert. We will never match or autofill a PasswordForm where + // ssl_valid == true with a PasswordForm where ssl_valid == false. This means + // passwords saved under HTTPS will never get autofilled onto an HTTP page. + // When importing, this should be set to true if the page URL is HTTPS, thus + // giving it "the benefit of the doubt" that the SSL cert was valid when it + // was saved. Default to false. + bool ssl_valid; + + // True if this PasswordForm represents the last username/password login the + // user selected to log in to the site. If there is only one saved entry for + // the site, this will always be true, but when there are multiple entries + // the PasswordManager ensures that only one of them has a preferred bit set + // to true. Default to false. + // + // When parsing an HTML form, this is not used. + bool preferred; + + // When the login was saved (by chrome). + // + // When parsing an HTML form, this is not used. + base::Time date_created; + + // Tracks if the user opted to never remember passwords for this form. Default + // to false. + // + // When parsing an HTML form, this is not used. + bool blacklisted_by_user; + + PasswordForm() + : scheme(SCHEME_HTML), + ssl_valid(false), + preferred(false), + blacklisted_by_user(false) { + } + + PasswordForm(const WebKit::WebPasswordFormData& web_password_form) + : scheme(SCHEME_HTML), + signon_realm(web_password_form.signonRealm.utf8()), + origin(web_password_form.origin), + action(web_password_form.action), + submit_element(web_password_form.submitElement), + username_element(web_password_form.userNameElement), + username_value(web_password_form.userNameValue), + password_element(web_password_form.passwordElement), + password_value(web_password_form.passwordValue), + old_password_element(web_password_form.oldPasswordElement), + old_password_value(web_password_form.oldPasswordValue), + ssl_valid(false), + preferred(false), + blacklisted_by_user(false) { + } +}; + +// Map username to PasswordForm* for convenience. See password_form_manager.h. +typedef std::map<string16, PasswordForm*> PasswordFormMap; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PASSWORD_FORM_H__ diff --git a/webkit/glue/password_form_dom_manager.cc b/webkit/glue/password_form_dom_manager.cc new file mode 100644 index 0000000..adbb8dd --- /dev/null +++ b/webkit/glue/password_form_dom_manager.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/password_form_dom_manager.h" + +#include "base/logging.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPasswordFormData.h" +#include "webkit/glue/form_field.h" + +using WebKit::WebFormElement; +using WebKit::WebInputElement; +using WebKit::WebPasswordFormData; + +namespace webkit_glue { + +PasswordFormFillData::PasswordFormFillData() : wait_for_username(false) { +} + +PasswordFormFillData::~PasswordFormFillData() { +} + +PasswordForm* PasswordFormDomManager::CreatePasswordForm( + const WebFormElement& webform) { + WebPasswordFormData web_password_form(webform); + if (web_password_form.isValid()) + return new PasswordForm(web_password_form); + return NULL; +} + +// static +void PasswordFormDomManager::InitFillData( + const PasswordForm& form_on_page, + const PasswordFormMap& matches, + const PasswordForm* const preferred_match, + bool wait_for_username_before_autofill, + PasswordFormFillData* result) { + DCHECK(preferred_match); + // Fill basic form data. + result->basic_data.origin = form_on_page.origin; + result->basic_data.action = form_on_page.action; + // TODO(jhawkins): Is it right to use an empty string for the form control + // type? I don't think the password autocomplete really cares, but we should + // correct this anyway. + // TODO(dhollowa): Similarly, |size| ideally should be set from the form + // control itself. But it is currently unused. + result->basic_data.fields.push_back( + FormField(string16(), + form_on_page.username_element, + preferred_match->username_value, + string16(), + 0)); + result->basic_data.fields.push_back( + FormField(string16(), + form_on_page.password_element, + preferred_match->password_value, + string16(), + 0)); + result->wait_for_username = wait_for_username_before_autofill; + + // Copy additional username/value pairs. + PasswordFormMap::const_iterator iter; + for (iter = matches.begin(); iter != matches.end(); iter++) { + if (iter->second != preferred_match) + result->additional_logins[iter->first] = iter->second->password_value; + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/password_form_dom_manager.h b/webkit/glue/password_form_dom_manager.h new file mode 100644 index 0000000..63a6f45 --- /dev/null +++ b/webkit/glue/password_form_dom_manager.h @@ -0,0 +1,63 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PASSWORD_FORM_DOM_MANAGER_H_ +#define WEBKIT_GLUE_PASSWORD_FORM_DOM_MANAGER_H_ + +#include "webkit/glue/form_data.h" +#include "webkit/glue/password_form.h" + +namespace WebKit { +class WebForm; +} + +class GURL; + +namespace webkit_glue { + +// Structure used for autofilling password forms. +// basic_data identifies the HTML form on the page and preferred username/ +// password for login, while +// additional_logins is a list of other matching user/pass pairs for the form. +// wait_for_username tells us whether we need to wait for the user to enter +// a valid username before we autofill the password. By default, this is off +// unless the PasswordManager determined there is an additional risk +// associated with this form. This can happen, for example, if action URI's +// of the observed form and our saved representation don't match up. +struct PasswordFormFillData { + typedef std::map<string16, string16> LoginCollection; + + FormData basic_data; + LoginCollection additional_logins; + bool wait_for_username; + PasswordFormFillData(); + ~PasswordFormFillData(); +}; + +class PasswordFormDomManager { + public: + // Create a PasswordForm from DOM form. Webkit doesn't allow storing + // custom metadata to DOM nodes, so we have to do this every time an event + // happens with a given form and compare against previously Create'd forms + // to identify..which sucks. + static PasswordForm* CreatePasswordForm(const WebKit::WebFormElement& form); + + // Create a FillData structure in preparation for autofilling a form, + // from basic_data identifying which form to fill, and a collection of + // matching stored logins to use as username/password values. + // preferred_match should equal (address) one of matches. + // wait_for_username_before_autofill is true if we should not autofill + // anything until the user typed in a valid username and blurred the field. + static void InitFillData(const PasswordForm& form_on_page, + const PasswordFormMap& matches, + const PasswordForm* const preferred_match, + bool wait_for_username_before_autofill, + PasswordFormFillData* result); + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(PasswordFormDomManager); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PASSWORD_FORM_DOM_MANAGER_H__ diff --git a/webkit/glue/plugins/DEPS b/webkit/glue/plugins/DEPS new file mode 100644 index 0000000..4ac8845 --- /dev/null +++ b/webkit/glue/plugins/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+printing", + "+third_party/ppapi/c", +] diff --git a/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc new file mode 100644 index 0000000..6131b45 --- /dev/null +++ b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "webkit/glue/plugins/carbon_plugin_window_tracker_mac.h" + +CarbonPluginWindowTracker::CarbonPluginWindowTracker() { +} + +CarbonPluginWindowTracker* CarbonPluginWindowTracker::SharedInstance() { + static CarbonPluginWindowTracker* tracker = new CarbonPluginWindowTracker(); + return tracker; +} + +WindowRef CarbonPluginWindowTracker::CreateDummyWindowForDelegate( + WebPluginDelegateImpl* delegate) { + // The real size will be set by the plugin instance, once that size is known. + Rect window_bounds = { 0, 0, 100, 100 }; + WindowRef new_ref = NULL; + if (CreateNewWindow(kDocumentWindowClass, + kWindowNoTitleBarAttribute, + &window_bounds, + &new_ref) == noErr) { + window_to_delegate_map_[new_ref] = delegate; + delegate_to_window_map_[delegate] = new_ref; + } + return new_ref; +} + +WebPluginDelegateImpl* CarbonPluginWindowTracker::GetDelegateForDummyWindow( + WindowRef window) const { + WindowToDelegateMap::const_iterator i = window_to_delegate_map_.find(window); + if (i != window_to_delegate_map_.end()) + return i->second; + return NULL; +} + +WindowRef CarbonPluginWindowTracker::GetDummyWindowForDelegate( + WebPluginDelegateImpl* delegate) const { + DelegateToWindowMap::const_iterator i = + delegate_to_window_map_.find(delegate); + if (i != delegate_to_window_map_.end()) + return i->second; + return NULL; +} + +void CarbonPluginWindowTracker::DestroyDummyWindowForDelegate( + WebPluginDelegateImpl* delegate, WindowRef window) { + DCHECK(GetDelegateForDummyWindow(window) == delegate); + window_to_delegate_map_.erase(window); + delegate_to_window_map_.erase(delegate); + if (window) // Check just in case the initial window creation failed. + DisposeWindow(window); +} diff --git a/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h new file mode 100644 index 0000000..3e6ef2d --- /dev/null +++ b/webkit/glue/plugins/carbon_plugin_window_tracker_mac.h @@ -0,0 +1,51 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ +#define WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ + +#include <Carbon/Carbon.h> +#include <map> + +#include "base/basictypes.h" + +class WebPluginDelegateImpl; + +// Creates and tracks the invisible windows that are necessary for +// Carbon-event-model plugins. +// +// Serves as a bridge between plugin delegate instances and the Carbon +// interposing library. The Carbon functions we interpose work in terms of +// WindowRefs, and we need to be able to map from those back to the plugin +// delegates that know what we should claim about the state of the window. +class __attribute__((visibility("default"))) CarbonPluginWindowTracker { + public: + CarbonPluginWindowTracker(); + + // Returns the shared window tracker instance. + static CarbonPluginWindowTracker* SharedInstance(); + + // Creates a new carbon window associated with |delegate|. + WindowRef CreateDummyWindowForDelegate(WebPluginDelegateImpl* delegate); + + // Returns the WebPluginDelegate associated with the given dummy window. + WebPluginDelegateImpl* GetDelegateForDummyWindow(WindowRef window) const; + + // Returns the dummy window associated with |delegate|. + WindowRef GetDummyWindowForDelegate(WebPluginDelegateImpl* delegate) const; + + // Destroys the dummy window for |delegate|. + void DestroyDummyWindowForDelegate(WebPluginDelegateImpl* delegate, + WindowRef window); + + private: + typedef std::map<WindowRef, WebPluginDelegateImpl*> WindowToDelegateMap; + typedef std::map<WebPluginDelegateImpl*, WindowRef> DelegateToWindowMap; + WindowToDelegateMap window_to_delegate_map_; + DelegateToWindowMap delegate_to_window_map_; + + DISALLOW_COPY_AND_ASSIGN(CarbonPluginWindowTracker); +}; + +#endif // WEBKIT_GLUE_PLUGINS_CARBON_PLUGIN_WINDOW_TRACKER_MAC_H_ diff --git a/webkit/glue/plugins/coregraphics_private_symbols_mac.h b/webkit/glue/plugins/coregraphics_private_symbols_mac.h new file mode 100644 index 0000000..0342d6f --- /dev/null +++ b/webkit/glue/plugins/coregraphics_private_symbols_mac.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ +#define WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ + +// These are CoreGraphics SPI, verified to exist in both 10.5 and 10.6. + +#ifdef __cplusplus +extern "C" { +#endif + +// Copies the contents of the window with id |wid| into the given rect in the +// given context +OSStatus CGContextCopyWindowCaptureContentsToRect( + CGContextRef, CGRect, int cid, int wid, int unknown); + +// Returns the connection ID we need for the third argument to +// CGContextCopyWindowCaptureContentsToRect +int _CGSDefaultConnection(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBKIT_GLUE_PLUGINS_COREGRAPHICS_PRIVATE_SYMBOLS_MAC_H_ diff --git a/webkit/glue/plugins/default_plugin_shared.h b/webkit/glue/plugins/default_plugin_shared.h new file mode 100644 index 0000000..79d06b3 --- /dev/null +++ b/webkit/glue/plugins/default_plugin_shared.h @@ -0,0 +1,31 @@ +// Copyright (c) 2006-2008 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. +// +// Thes file contains stuff that should be shared among projects that do some +// special handling with default plugin + +#ifndef WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H +#define WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H + +namespace default_plugin { + +// We use the NPNGetValue host function to send notification message to host. +// This corresponds to NPNVariable defined in npapi.h, and should be chosen so +// as to not overlap values if NPAPI is updated. + +const int kMissingPluginStatusStart = 5000; + +enum MissingPluginStatus { + MISSING_PLUGIN_AVAILABLE, + MISSING_PLUGIN_USER_STARTED_DOWNLOAD +}; + +#if defined(OS_WIN) +#include <windows.h> +const int kInstallMissingPluginMessage = WM_APP + 117; +#endif + +} // namespace default_plugin + +#endif // WEBKIT_GLUE_PLUGINS_DEFAULT_PLUGIN_SHARED_H diff --git a/webkit/glue/plugins/gtk_plugin_container.cc b/webkit/glue/plugins/gtk_plugin_container.cc new file mode 100644 index 0000000..c80bbf1 --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/gtk_plugin_container.h" + +#include <gtk/gtk.h> + +#include "base/basictypes.h" + +namespace { + +// NOTE: This class doesn't have constructors/destructors, it is created +// through GLib's object management. +class GtkPluginContainer : public GtkSocket { + public: + // Sets the requested size of the widget. + void set_size(int width, int height) { + width_ = width; + height_ = height; + } + + // Casts a widget into a GtkPluginContainer, after checking the type. + template <class T> + static GtkPluginContainer *CastChecked(T *instance) { + return G_TYPE_CHECK_INSTANCE_CAST(instance, GetType(), GtkPluginContainer); + } + + // Create and register our custom container type with GTK. + static GType GetType() { + static GType type = 0; // We only want to register our type once. + if (!type) { + static const GTypeInfo info = { + sizeof(GtkSocketClass), + NULL, NULL, + static_cast<GClassInitFunc>(&ClassInit), + NULL, NULL, + sizeof(GtkPluginContainer), + 0, &InstanceInit, + }; + type = g_type_register_static(GTK_TYPE_SOCKET, + "GtkPluginContainer", + &info, + static_cast<GTypeFlags>(0)); + } + return type; + } + + // Implementation of the class initializer. + static void ClassInit(gpointer klass, gpointer class_data_unusued) { + GtkWidgetClass* widget_class = reinterpret_cast<GtkWidgetClass*>(klass); + widget_class->size_request = &HandleSizeRequest; + } + + // Implementation of the instance initializer (constructor). + static void InstanceInit(GTypeInstance *instance, gpointer klass) { + GtkPluginContainer *container = CastChecked(instance); + container->set_size(0, 0); + } + + // Report our allocation size during size requisition. + static void HandleSizeRequest(GtkWidget* widget, + GtkRequisition* requisition) { + GtkPluginContainer *container = CastChecked(widget); + requisition->width = container->width_; + requisition->height = container->height_; + } + + int width_; + int height_; + DISALLOW_IMPLICIT_CONSTRUCTORS(GtkPluginContainer); +}; + +} // anonymous namespace + +// Create a new instance of our GTK widget object. +GtkWidget* gtk_plugin_container_new() { + return GTK_WIDGET(g_object_new(GtkPluginContainer::GetType(), NULL)); +} + +void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height) { + GtkPluginContainer::CastChecked(widget)->set_size(width, height); + // Signal the parent that the size request has changed. + gtk_widget_queue_resize_no_redraw(widget); +} diff --git a/webkit/glue/plugins/gtk_plugin_container.h b/webkit/glue/plugins/gtk_plugin_container.h new file mode 100644 index 0000000..eed6b94 --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container.h @@ -0,0 +1,26 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ +#define WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ + +// Windowed plugins are embedded via XEmbed, which is implemented by +// GtkPlug/GtkSocket. But we want to control sizing and positioning +// directly, so we need a subclass of GtkSocket that sidesteps the +// size_request handler. +// +// The custom size_request handler just reports the size set by +// gtk_plugin_container_set_size. + +typedef struct _GtkWidget GtkWidget; + +// Return a new GtkPluginContainer. +// Intentionally GTK-style here since we're creating a custom GTK widget. +// This is a GtkSocket subclass; see its documentation for available methods. +GtkWidget* gtk_plugin_container_new(); + +// Sets the size of the GtkPluginContainer. +void gtk_plugin_container_set_size(GtkWidget *widget, int width, int height); + +#endif // WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_H_ diff --git a/webkit/glue/plugins/gtk_plugin_container_manager.cc b/webkit/glue/plugins/gtk_plugin_container_manager.cc new file mode 100644 index 0000000..9d9ee4b --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container_manager.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/gtk_plugin_container_manager.h" + +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "gfx/gtk_util.h" +#include "webkit/glue/plugins/gtk_plugin_container.h" +#include "webkit/glue/plugins/webplugin.h" + +GtkWidget* GtkPluginContainerManager::CreatePluginContainer( + gfx::PluginWindowHandle id) { + DCHECK(host_widget_); + GtkWidget *widget = gtk_plugin_container_new(); + plugin_window_to_widget_map_.insert(std::make_pair(id, widget)); + + // The Realize callback is responsible for adding the plug into the socket. + // The reason is 2-fold: + // - the plug can't be added until the socket is realized, but this may not + // happen until the socket is attached to a top-level window, which isn't the + // case for background tabs. + // - when dragging tabs, the socket gets unrealized, which breaks the XEMBED + // connection. We need to make it again when the tab is reattached, and the + // socket gets realized again. + // + // Note, the RealizeCallback relies on the plugin_window_to_widget_map_ to + // have the mapping. + g_signal_connect(widget, "realize", + G_CALLBACK(RealizeCallback), this); + + // Don't destroy the widget when the plug is removed. + g_signal_connect(widget, "plug-removed", + G_CALLBACK(gtk_true), NULL); + + gtk_container_add(GTK_CONTAINER(host_widget_), widget); + gtk_widget_show(widget); + + return widget; +} + +void GtkPluginContainerManager::DestroyPluginContainer( + gfx::PluginWindowHandle id) { + DCHECK(host_widget_); + GtkWidget* widget = MapIDToWidget(id); + if (widget) + gtk_widget_destroy(widget); + + plugin_window_to_widget_map_.erase(id); +} + +void GtkPluginContainerManager::MovePluginContainer( + const webkit_glue::WebPluginGeometry& move) { + DCHECK(host_widget_); + GtkWidget *widget = MapIDToWidget(move.window); + if (!widget) + return; + + DCHECK(!GTK_WIDGET_NO_WINDOW(widget)); + + if (!move.visible) { + gtk_widget_hide(widget); + return; + } + + DCHECK(GTK_WIDGET_REALIZED(widget)); + gtk_widget_show(widget); + + if (!move.rects_valid) + return; + + GdkRectangle clip_rect = move.clip_rect.ToGdkRectangle(); + GdkRegion* clip_region = gdk_region_rectangle(&clip_rect); + gfx::SubtractRectanglesFromRegion(clip_region, move.cutout_rects); + gdk_window_shape_combine_region(widget->window, clip_region, 0, 0); + gdk_region_destroy(clip_region); + + // Update the window position. Resizing is handled by WebPluginDelegate. + // TODO(deanm): Verify that we only need to move and not resize. + // TODO(evanm): we should cache the last shape and position and skip all + // of this business in the common case where nothing has changed. + int current_x, current_y; + + // Until the above TODO is resolved, we can grab the last position + // off of the GtkFixed with a bit of hackery. + GValue value = {0}; + g_value_init(&value, G_TYPE_INT); + gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget, + "x", &value); + current_x = g_value_get_int(&value); + gtk_container_child_get_property(GTK_CONTAINER(host_widget_), widget, + "y", &value); + current_y = g_value_get_int(&value); + g_value_unset(&value); + + if (move.window_rect.x() != current_x || + move.window_rect.y() != current_y) { + // Calling gtk_fixed_move unnecessarily is a no-no, as it causes the + // parent window to repaint! + gtk_fixed_move(GTK_FIXED(host_widget_), + widget, + move.window_rect.x(), + move.window_rect.y()); + } + + gtk_plugin_container_set_size(widget, + move.window_rect.width(), + move.window_rect.height()); +} + +GtkWidget* GtkPluginContainerManager::MapIDToWidget( + gfx::PluginWindowHandle id) { + PluginWindowToWidgetMap::const_iterator i = + plugin_window_to_widget_map_.find(id); + if (i != plugin_window_to_widget_map_.end()) + return i->second; + + LOG(ERROR) << "Request for widget host for unknown window id " << id; + + return NULL; +} + +gfx::PluginWindowHandle GtkPluginContainerManager::MapWidgetToID( + GtkWidget* widget) { + for (PluginWindowToWidgetMap::const_iterator i = + plugin_window_to_widget_map_.begin(); + i != plugin_window_to_widget_map_.end(); ++i) { + if (i->second == widget) + return i->first; + } + + LOG(ERROR) << "Request for id for unknown widget"; + return 0; +} + +// static +void GtkPluginContainerManager::RealizeCallback(GtkWidget* widget, + void* user_data) { + GtkPluginContainerManager* plugin_container_manager = + static_cast<GtkPluginContainerManager*>(user_data); + + gfx::PluginWindowHandle id = plugin_container_manager->MapWidgetToID(widget); + if (id) + gtk_socket_add_id(GTK_SOCKET(widget), id); +} diff --git a/webkit/glue/plugins/gtk_plugin_container_manager.h b/webkit/glue/plugins/gtk_plugin_container_manager.h new file mode 100644 index 0000000..c33099d --- /dev/null +++ b/webkit/glue/plugins/gtk_plugin_container_manager.h @@ -0,0 +1,56 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ +#define WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ + +#include <gtk/gtk.h> +#include <map> + +#include "gfx/native_widget_types.h" + +typedef struct _GtkWidget GtkWidget; + +namespace webkit_glue { +struct WebPluginGeometry; +} + +// Helper class that creates and manages plugin containers (GtkSocket). +class GtkPluginContainerManager { + public: + GtkPluginContainerManager() : host_widget_(NULL) { } + + // Sets the widget that will host the plugin containers. Must be a GtkFixed. + void set_host_widget(GtkWidget *widget) { host_widget_ = widget; } + + // Creates a new plugin container, for a given plugin XID. + GtkWidget* CreatePluginContainer(gfx::PluginWindowHandle id); + + // Destroys a plugin container, given the plugin XID. + void DestroyPluginContainer(gfx::PluginWindowHandle id); + + // Takes an update from WebKit about a plugin's position and side and moves + // the plugin accordingly. + void MovePluginContainer(const webkit_glue::WebPluginGeometry& move); + + private: + // Maps a plugin XID to the corresponding container widget. + GtkWidget* MapIDToWidget(gfx::PluginWindowHandle id); + + // Maps a container widget to the corresponding plugin XID. + gfx::PluginWindowHandle MapWidgetToID(GtkWidget* widget); + + // Callback for when the plugin container gets realized, at which point it + // plugs the plugin XID. + static void RealizeCallback(GtkWidget *widget, void *user_data); + + // Parent of the plugin containers. + GtkWidget* host_widget_; + + // A map that associates plugin containers to the plugin XID. + typedef std::map<gfx::PluginWindowHandle, GtkWidget*> PluginWindowToWidgetMap; + PluginWindowToWidgetMap plugin_window_to_widget_map_; +}; + +#endif // WEBKIT_GLUE_PLUGINS_GTK_PLUGIN_CONTAINER_MANAGER_H_ diff --git a/webkit/glue/plugins/mac_accelerated_surface_container.cc b/webkit/glue/plugins/mac_accelerated_surface_container.cc new file mode 100644 index 0000000..8eca9d2 --- /dev/null +++ b/webkit/glue/plugins/mac_accelerated_surface_container.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/mac_accelerated_surface_container.h" + +#include "app/surface/io_surface_support_mac.h" +#include "base/logging.h" +#include "webkit/glue/plugins/mac_accelerated_surface_container_manager.h" +#include "webkit/glue/plugins/webplugin.h" + +MacAcceleratedSurfaceContainer::MacAcceleratedSurfaceContainer() + : x_(0), + y_(0), + surface_(NULL), + width_(0), + height_(0), + texture_(0) { +} + +MacAcceleratedSurfaceContainer::~MacAcceleratedSurfaceContainer() { + ReleaseIOSurface(); +} + +void MacAcceleratedSurfaceContainer::ReleaseIOSurface() { + if (surface_) { + CFRelease(surface_); + surface_ = NULL; + } +} + +void MacAcceleratedSurfaceContainer::SetSizeAndIOSurface( + int32 width, + int32 height, + uint64 io_surface_identifier, + MacAcceleratedSurfaceContainerManager* manager) { + ReleaseIOSurface(); + IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); + if (io_surface_support) { + surface_ = io_surface_support->IOSurfaceLookup( + static_cast<uint32>(io_surface_identifier)); + EnqueueTextureForDeletion(manager); + width_ = width; + height_ = height; + } +} + +void MacAcceleratedSurfaceContainer::SetSizeAndTransportDIB( + int32 width, + int32 height, + TransportDIB::Handle transport_dib, + MacAcceleratedSurfaceContainerManager* manager) { + if (TransportDIB::is_valid(transport_dib)) { + transport_dib_.reset(TransportDIB::Map(transport_dib)); + EnqueueTextureForDeletion(manager); + width_ = width; + height_ = height; + } +} + +void MacAcceleratedSurfaceContainer::MoveTo( + const webkit_glue::WebPluginGeometry& geom) { + x_ = geom.window_rect.x(); + y_ = geom.window_rect.y(); + // TODO(kbr): may need to pay attention to cutout rects. + clipRect_ = geom.clip_rect; +} + +void MacAcceleratedSurfaceContainer::Draw(CGLContextObj context) { + IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize(); + GLenum target = GL_TEXTURE_RECTANGLE_ARB; + if (!texture_) { + if ((io_surface_support && !surface_) || + (!io_surface_support && !transport_dib_.get())) + return; + glGenTextures(1, &texture_); + glBindTexture(target, texture_); + glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + // When using an IOSurface, the texture does not need to be repeatedly + // uploaded, so bind the IOSurface once during texture gen in this case. + if (io_surface_support) { + DCHECK(surface_); + // Don't think we need to identify a plane. + GLuint plane = 0; + io_surface_support->CGLTexImageIOSurface2D(context, + target, + GL_RGBA, + width_, + height_, + GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, + surface_, + plane); + } else { + // Reserve space on the card for the actual texture upload, done with the + // glTexSubImage2D() call, below. + glTexImage2D(target, + 0, // mipmap level 0 + GL_RGBA, // internal format + width_, + height_, + 0, // no border + GL_BGRA, // The GPU plugin read BGRA pixels + GL_UNSIGNED_INT_8_8_8_8_REV, + NULL); // No data, this call just reserves room. + } + } + + // If using TransportDIBs, the texture needs to be uploaded every frame. + if (transport_dib_.get() != NULL) { + void* pixel_memory = transport_dib_->memory(); + if (pixel_memory) { + glBindTexture(target, texture_); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Needed for NPOT textures. + glTexSubImage2D(target, + 0, // mipmap level 0 + 0, // x-offset + 0, // y-offset + width_, + height_, + GL_BGRA, // The GPU plugin gave us BGRA pixels + GL_UNSIGNED_INT_8_8_8_8_REV, + pixel_memory); + } + } + + if (texture_) { + // TODO(kbr): convert this to use only OpenGL ES 2.0 functionality + glBindTexture(target, texture_); + glEnable(target); + glBegin(GL_TRIANGLE_STRIP); + // TODO(kbr): may need to pay attention to cutout rects. + int clipX = clipRect_.x(); + int clipY = clipRect_.y(); + int clipWidth = clipRect_.width(); + int clipHeight = clipRect_.height(); + int x = x_ + clipX; + int y = y_ + clipY; + glTexCoord2f(clipX, height_ - clipY); + glVertex3f(x, y, 0); + glTexCoord2f(clipX + clipWidth, height_ - clipY); + glVertex3f(x + clipWidth, y, 0); + glTexCoord2f(clipX, height_ - clipY - clipHeight); + glVertex3f(x, y + clipHeight, 0); + glTexCoord2f(clipX + clipWidth, height_ - clipY - clipHeight); + glVertex3f(x + clipWidth, y + clipHeight, 0); + glEnd(); + glDisable(target); + } +} + +void MacAcceleratedSurfaceContainer::EnqueueTextureForDeletion( + MacAcceleratedSurfaceContainerManager* manager) { + manager->EnqueueTextureForDeletion(texture_); + texture_ = 0; +} + diff --git a/webkit/glue/plugins/mac_accelerated_surface_container.h b/webkit/glue/plugins/mac_accelerated_surface_container.h new file mode 100644 index 0000000..0fd1793 --- /dev/null +++ b/webkit/glue/plugins/mac_accelerated_surface_container.h @@ -0,0 +1,110 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_H_ +#define WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_H_ + +// The "GPU plugin" is currently implemented as a special kind of +// NPAPI plugin to provide high-performance on-screen 3D rendering for +// Pepper 3D. +// +// On Windows and X11 platforms the GPU plugin relies on cross-process +// parenting of windows, which is not supported via any public APIs in +// the Mac OS X window system. +// +// To achieve full hardware acceleration we use the new IOSurface APIs +// introduced in Mac OS X 10.6. The GPU plugin's process produces an +// IOSurface and renders into it using OpenGL. It uses the +// IOSurfaceGetID and IOSurfaceLookup APIs to pass a reference to this +// surface to the browser process for on-screen rendering. The GPU +// plugin essentially looks like a windowless plugin; the browser +// process gets all of the mouse events, because the plugin process +// does not have an on-screen window. +// +// This class encapsulates some of the management of these data +// structures, in conjunction with the MacAcceleratedSurfaceContainerManager. + +#include <CoreFoundation/CoreFoundation.h> +#include <OpenGL/OpenGL.h> + +#include "app/gfx/native_widget_types.h" +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/gfx/rect.h" + +namespace webkit_glue { +struct WebPluginGeometry; +} + +class MacAcceleratedSurfaceContainerManager; + +class MacAcceleratedSurfaceContainer { + public: + MacAcceleratedSurfaceContainer(); + virtual ~MacAcceleratedSurfaceContainer(); + + // Sets the backing store and size of this accelerated surface container. + // There are two versions: the IOSurface version is used on systems where the + // IOSurface API is supported (Mac OS X 10.6 and later); the TransportDIB is + // used on Mac OS X 10.5 and earlier. + void SetSizeAndIOSurface(int32 width, + int32 height, + uint64 io_surface_identifier, + MacAcceleratedSurfaceContainerManager* manager); + void SetSizeAndTransportDIB(int32 width, + int32 height, + TransportDIB::Handle transport_dib, + MacAcceleratedSurfaceContainerManager* manager); + + // Tells the accelerated surface container that it has moved relative to the + // origin of the window, for example because of a scroll event. + void MoveTo(const webkit_glue::WebPluginGeometry& geom); + + // Draws this accelerated surface's contents, texture mapped onto a quad in + // the given OpenGL context. TODO(kbr): figure out and define exactly how the + // coordinate system will work out. + void Draw(CGLContextObj context); + + // Enqueue our texture for later deletion. Call this before deleting + // this object. + void EnqueueTextureForDeletion(MacAcceleratedSurfaceContainerManager* manager); + + private: + // The x and y coordinates of the plugin window on the web page. + int x_; + int y_; + + void ReleaseIOSurface(); + + // The IOSurfaceRef, if any, that has been handed from the GPU + // plugin process back to the browser process for drawing. + // This is held as a CFTypeRef because we can't refer to the + // IOSurfaceRef type when building on 10.5. + CFTypeRef surface_; + + // The TransportDIB which is used in pre-10.6 systems where the IOSurface + // API is not supported. This is a weak reference to the actual TransportDIB + // whic is owned by the GPU process. + scoped_ptr<TransportDIB> transport_dib_; + + // The width and height of the surface. + int32 width_; + int32 height_; + + // The clip rectangle, relative to the (x_, y_) origin. + gfx::Rect clipRect_; + + // The "live" OpenGL texture referring to this IOSurfaceRef. Note + // that per the CGLTexImageIOSurface2D API we do not need to + // explicitly update this texture's contents once created. All we + // need to do is ensure it is re-bound before attempting to draw + // with it. + GLuint texture_; + + DISALLOW_COPY_AND_ASSIGN(MacAcceleratedSurfaceContainer); +}; + +#endif // WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_H_ + diff --git a/webkit/glue/plugins/mac_accelerated_surface_container_manager.cc b/webkit/glue/plugins/mac_accelerated_surface_container_manager.cc new file mode 100644 index 0000000..635348f --- /dev/null +++ b/webkit/glue/plugins/mac_accelerated_surface_container_manager.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/mac_accelerated_surface_container_manager.h" + +#include "base/logging.h" +#include "webkit/glue/plugins/mac_accelerated_surface_container.h" +#include "webkit/glue/plugins/webplugin.h" + +MacAcceleratedSurfaceContainerManager::MacAcceleratedSurfaceContainerManager() + : current_id_(0) { +} + +gfx::PluginWindowHandle +MacAcceleratedSurfaceContainerManager::AllocateFakePluginWindowHandle() { + MacAcceleratedSurfaceContainer* container = + new MacAcceleratedSurfaceContainer(); + gfx::PluginWindowHandle res = + static_cast<gfx::PluginWindowHandle>(++current_id_); + plugin_window_to_container_map_.insert(std::make_pair(res, container)); + return res; +} + +void MacAcceleratedSurfaceContainerManager::DestroyFakePluginWindowHandle( + gfx::PluginWindowHandle id) { + MacAcceleratedSurfaceContainer* container = MapIDToContainer(id); + if (container) + delete container; + plugin_window_to_container_map_.erase(id); +} + +void MacAcceleratedSurfaceContainerManager::SetSizeAndIOSurface( + gfx::PluginWindowHandle id, + int32 width, + int32 height, + uint64 io_surface_identifier) { + MacAcceleratedSurfaceContainer* container = MapIDToContainer(id); + if (container) + container->SetSizeAndIOSurface(width, height, + io_surface_identifier, this); +} + +void MacAcceleratedSurfaceContainerManager::SetSizeAndTransportDIB( + gfx::PluginWindowHandle id, + int32 width, + int32 height, + TransportDIB::Handle transport_dib) { + MacAcceleratedSurfaceContainer* container = MapIDToContainer(id); + if (container) + container->SetSizeAndTransportDIB(width, height, + transport_dib, this); +} + +void MacAcceleratedSurfaceContainerManager::MovePluginContainer( + const webkit_glue::WebPluginGeometry& move) { + MacAcceleratedSurfaceContainer* container = MapIDToContainer(move.window); + if (container) + container->MoveTo(move); +} + +void MacAcceleratedSurfaceContainerManager::Draw(CGLContextObj context) { + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + GLenum target = GL_TEXTURE_RECTANGLE_ARB; + glTexEnvi(target, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + for (PluginWindowToContainerMap::const_iterator i = + plugin_window_to_container_map_.begin(); + i != plugin_window_to_container_map_.end(); ++i) { + MacAcceleratedSurfaceContainer* container = i->second; + container->Draw(context); + } + + // Unbind any texture from the texture target to ensure that the + // next time through we will have to re-bind the texture and thereby + // pick up modifications from the other process. + glBindTexture(target, 0); + + glFlush(); +} + +void MacAcceleratedSurfaceContainerManager::EnqueueTextureForDeletion( + GLuint texture) { + if (texture) { + textures_pending_deletion_.push_back(texture); + } +} + +MacAcceleratedSurfaceContainer* + MacAcceleratedSurfaceContainerManager::MapIDToContainer( + gfx::PluginWindowHandle id) { + PluginWindowToContainerMap::const_iterator i = + plugin_window_to_container_map_.find(id); + if (i != plugin_window_to_container_map_.end()) + return i->second; + + LOG(ERROR) << "Request for plugin container for unknown window id " << id; + + return NULL; +} + diff --git a/webkit/glue/plugins/mac_accelerated_surface_container_manager.h b/webkit/glue/plugins/mac_accelerated_surface_container_manager.h new file mode 100644 index 0000000..da86e36 --- /dev/null +++ b/webkit/glue/plugins/mac_accelerated_surface_container_manager.h @@ -0,0 +1,78 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_MANAGER_H_ +#define WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_MANAGER_H_ + +#include <OpenGL/OpenGL.h> +#include <map> +#include <vector> + +#include "app/gfx/native_widget_types.h" +#include "app/surface/transport_dib.h" +#include "base/basictypes.h" + +namespace webkit_glue { +struct WebPluginGeometry; +} + +class MacAcceleratedSurfaceContainer; + +// Helper class that manages the backing store and on-screen rendering +// of instances of the GPU plugin on the Mac. +class MacAcceleratedSurfaceContainerManager { + public: + MacAcceleratedSurfaceContainerManager(); + + // Allocates a new "fake" PluginWindowHandle, which is used as the + // key for the other operations. + gfx::PluginWindowHandle AllocateFakePluginWindowHandle(); + + // Destroys a fake PluginWindowHandle and associated storage. + void DestroyFakePluginWindowHandle(gfx::PluginWindowHandle id); + + // Sets the size and backing store of the plugin instance. There are two + // versions: the IOSurface version is used on systems where the IOSurface + // API is supported (Mac OS X 10.6 and later); the TransportDIB is used on + // Mac OS X 10.5 and earlier. + void SetSizeAndIOSurface(gfx::PluginWindowHandle id, + int32 width, + int32 height, + uint64 io_surface_identifier); + void SetSizeAndTransportDIB(gfx::PluginWindowHandle id, + int32 width, + int32 height, + TransportDIB::Handle transport_dib); + + // Takes an update from WebKit about a plugin's position and size and moves + // the plugin accordingly. + void MovePluginContainer(const webkit_glue::WebPluginGeometry& move); + + // Draws all of the managed plugin containers into the given OpenGL + // context, which must already be current. + void Draw(CGLContextObj context); + + // Called by the container to enqueue its OpenGL texture objects for + // deletion. + void EnqueueTextureForDeletion(GLuint texture); + + private: + uint32 current_id_; + + // Maps a "fake" plugin window handle to the corresponding container. + MacAcceleratedSurfaceContainer* MapIDToContainer(gfx::PluginWindowHandle id); + + // A map that associates plugin window handles with their containers. + typedef std::map<gfx::PluginWindowHandle, MacAcceleratedSurfaceContainer*> + PluginWindowToContainerMap; + PluginWindowToContainerMap plugin_window_to_container_map_; + + // A list of OpenGL textures waiting to be deleted + std::vector<GLuint> textures_pending_deletion_; + + DISALLOW_COPY_AND_ASSIGN(MacAcceleratedSurfaceContainerManager); +}; + +#endif // WEBKIT_GLUE_PLUGINS_MAC_ACCELERATED_SURFACE_CONTAINER_MANAGER_H_ + diff --git a/webkit/glue/plugins/npapi_extension_thunk.cc b/webkit/glue/plugins/npapi_extension_thunk.cc new file mode 100644 index 0000000..4779535 --- /dev/null +++ b/webkit/glue/plugins/npapi_extension_thunk.cc @@ -0,0 +1,551 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/npapi_extension_thunk.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/webkit_glue.h" + +// FindInstance() +// Finds a PluginInstance from an NPP. +// The caller must take a reference if needed. +static NPAPI::PluginInstance* FindInstance(NPP id) { + if (id == NULL) { + NOTREACHED(); + return NULL; + } + return static_cast<NPAPI::PluginInstance*>(id->ndata); +} + +// 2D device API --------------------------------------------------------------- + +static NPError Device2DQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + plugin->webplugin()->delegate()->Device2DQueryCapability(capability, value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError Device2DQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DQueryConfig( + static_cast<const NPDeviceContext2DConfig*>(request), + static_cast<NPDeviceContext2DConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DInitializeContext( + static_cast<const NPDeviceContext2DConfig*>(config), + static_cast<NPDeviceContext2D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DSetStateContext( + static_cast<NPDeviceContext2D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DGetStateContext( + static_cast<NPDeviceContext2D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + NPError err = plugin->webplugin()->delegate()->Device2DFlushContext( + id, static_cast<NPDeviceContext2D*>(context), callback, user_data); + + // Invoke the callback to inform the caller the work was done. + // TODO(brettw) this is probably not how we want this to work, this should + // happen when the frame is painted so the plugin knows when it can draw + // the next frame. + if (callback != NULL) + (*callback)(id, context, err, user_data); + + // Return any errors. + return err; + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device2DDestroyContext( + static_cast<NPDeviceContext2D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DCreateBuffer(NPP id, + NPDeviceContext* context, + size_t size, + int32_t* buffer_id) { + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DDestroyBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id) { + return NPERR_GENERIC_ERROR; +} + +static NPError Device2DMapBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id, + NPDeviceBuffer* buffer) { + return NPERR_GENERIC_ERROR; +} + +// 3D device API --------------------------------------------------------------- + +static NPError Device3DQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + plugin->webplugin()->delegate()->Device3DQueryCapability(capability, value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError Device3DQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DQueryConfig( + static_cast<const NPDeviceContext3DConfig*>(request), + static_cast<NPDeviceContext3DConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DInitializeContext( + static_cast<const NPDeviceContext3DConfig*>(config), + static_cast<NPDeviceContext3D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DSetStateContext( + static_cast<NPDeviceContext3D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetStateContext( + static_cast<NPDeviceContext3D*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DFlushContext( + id, static_cast<NPDeviceContext3D*>(context), callback, user_data); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DDestroyContext( + static_cast<NPDeviceContext3D*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DCreateBuffer(NPP id, + NPDeviceContext* context, + size_t size, + int32_t* buffer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DCreateBuffer( + static_cast<NPDeviceContext3D*>(context), size, buffer_id); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DDestroyBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DDestroyBuffer( + static_cast<NPDeviceContext3D*>(context), buffer_id); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DMapBuffer(NPP id, + NPDeviceContext* context, + int32_t buffer_id, + NPDeviceBuffer* buffer) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DMapBuffer( + static_cast<NPDeviceContext3D*>(context), buffer_id, buffer); + } + return NPERR_GENERIC_ERROR; +} + +// Experimental 3D device API -------------------------------------------------- + +static NPError Device3DGetNumConfigs(NPP id, int32_t* num_configs) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetNumConfigs(num_configs); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DGetConfigAttribs(NPP id, + int32_t config, + int32_t* attrib_list) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DGetConfigAttribs( + config, + attrib_list); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DCreateContext(NPP id, + int32_t config, + const int32_t* attrib_list, + NPDeviceContext** context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DCreateContext( + config, + attrib_list, + reinterpret_cast<NPDeviceContext3D**>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DSynchronizeContext( + NPP id, + NPDeviceContext* context, + NPDeviceSynchronizationMode mode, + const int32_t* input_attrib_list, + int32_t* output_attrib_list, + NPDeviceSynchronizeContextCallbackPtr callback, + void* callback_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DSynchronizeContext( + id, + static_cast<NPDeviceContext3D*>(context), + mode, + input_attrib_list, + output_attrib_list, + callback, + callback_data); + } + return NPERR_GENERIC_ERROR; +} + +static NPError Device3DRegisterCallback( + NPP id, + NPDeviceContext* context, + int32_t callback_type, + NPDeviceGenericCallbackPtr callback, + void* callback_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->Device3DRegisterCallback( + id, + static_cast<NPDeviceContext3D*>(context), + callback_type, + callback, + callback_data); + } + return NPERR_GENERIC_ERROR; +} + +// Audio device API ------------------------------------------------------------ + +static NPError DeviceAudioQueryCapability(NPP id, int32_t capability, + int32_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + plugin->webplugin()->delegate()->DeviceAudioQueryCapability(capability, + value); + return NPERR_NO_ERROR; + } else { + return NPERR_GENERIC_ERROR; + } +} + +static NPError DeviceAudioQueryConfig(NPP id, + const NPDeviceConfig* request, + NPDeviceConfig* obtain) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioQueryConfig( + static_cast<const NPDeviceContextAudioConfig*>(request), + static_cast<NPDeviceContextAudioConfig*>(obtain)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioInitializeContext(NPP id, + const NPDeviceConfig* config, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioInitializeContext( + static_cast<const NPDeviceContextAudioConfig*>(config), + static_cast<NPDeviceContextAudio*>(context)); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioSetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + return plugin->webplugin()->delegate()->DeviceAudioSetStateContext( + static_cast<NPDeviceContextAudio*>(context), state, value); + } + return NPERR_GENERIC_ERROR; +} + +static NPError DeviceAudioGetStateContext(NPP id, + NPDeviceContext* context, + int32_t state, + intptr_t* value) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + return plugin->webplugin()->delegate()->DeviceAudioGetStateContext( + static_cast<NPDeviceContextAudio*>(context), state, value); +} + +static NPError DeviceAudioFlushContext(NPP id, + NPDeviceContext* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + return plugin->webplugin()->delegate()->DeviceAudioFlushContext( + id, static_cast<NPDeviceContextAudio*>(context), callback, user_data); +} + +static NPError DeviceAudioDestroyContext(NPP id, + NPDeviceContext* context) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + return plugin->webplugin()->delegate()->DeviceAudioDestroyContext( + static_cast<NPDeviceContextAudio*>(context)); +} +// ----------------------------------------------------------------------------- + +static NPDevice* AcquireDevice(NPP id, NPDeviceID device_id) { + static NPDevice device_2d = { + Device2DQueryCapability, + Device2DQueryConfig, + Device2DInitializeContext, + Device2DSetStateContext, + Device2DGetStateContext, + Device2DFlushContext, + Device2DDestroyContext, + Device2DCreateBuffer, + Device2DDestroyBuffer, + Device2DMapBuffer, + NULL, + NULL, + NULL, + NULL, + NULL, + }; + static NPDevice device_3d = { + Device3DQueryCapability, + Device3DQueryConfig, + Device3DInitializeContext, + Device3DSetStateContext, + Device3DGetStateContext, + Device3DFlushContext, + Device3DDestroyContext, + Device3DCreateBuffer, + Device3DDestroyBuffer, + Device3DMapBuffer, + Device3DGetNumConfigs, + Device3DGetConfigAttribs, + Device3DCreateContext, + Device3DRegisterCallback, + Device3DSynchronizeContext, + }; + static NPDevice device_audio = { + DeviceAudioQueryCapability, + DeviceAudioQueryConfig, + DeviceAudioInitializeContext, + DeviceAudioSetStateContext, + DeviceAudioGetStateContext, + DeviceAudioFlushContext, + DeviceAudioDestroyContext, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + }; + + switch (device_id) { + case NPPepper2DDevice: + return const_cast<NPDevice*>(&device_2d); + case NPPepper3DDevice: + return const_cast<NPDevice*>(&device_3d); + case NPPepperAudioDevice: + return const_cast<NPDevice*>(&device_audio); + default: + return NULL; + } +} + +static NPError ChooseFile(NPP id, + const char* mime_types, + NPChooseFileMode mode, + NPChooseFileCallback callback, + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NPERR_GENERIC_ERROR; + + if (!plugin->webplugin()->delegate()->ChooseFile(mime_types, + static_cast<int>(mode), + callback, user_data)) + return NPERR_GENERIC_ERROR; + + return NPERR_NO_ERROR; +} + +static void NumberOfFindResultsChanged(NPP id, int total, bool final_result) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) { + plugin->webplugin()->delegate()->NumberOfFindResultsChanged( + total, final_result); + } +} + +static void SelectedFindResultChanged(NPP id, int index) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) + plugin->webplugin()->delegate()->SelectedFindResultChanged(index); +} + +static NPWidgetExtensions* GetWidgetExtensions(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NULL; + + return plugin->webplugin()->delegate()->GetWidgetExtensions(); +} + +static NPError NPSetCursor(NPP id, NPCursorType type) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NPERR_GENERIC_ERROR; + + return plugin->webplugin()->delegate()->SetCursor(type) ? + NPERR_NO_ERROR : NPERR_GENERIC_ERROR; +} + +static NPFontExtensions* GetFontExtensions(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NULL; + + return plugin->webplugin()->delegate()->GetFontExtensions(); +} + +namespace NPAPI { + +NPError GetPepperExtensionsFunctions(void* value) { + static const NPNExtensions kExtensions = { + &AcquireDevice, + &NumberOfFindResultsChanged, + &SelectedFindResultChanged, + &ChooseFile, + &GetWidgetExtensions, + &NPSetCursor, + &GetFontExtensions, + }; + + // Return a pointer to the canonical function table. + NPNExtensions* extensions = const_cast<NPNExtensions*>(&kExtensions); + NPNExtensions** exts = reinterpret_cast<NPNExtensions**>(value); + *exts = extensions; + return NPERR_NO_ERROR; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/npapi_extension_thunk.h b/webkit/glue/plugins/npapi_extension_thunk.h new file mode 100644 index 0000000..fada6bc --- /dev/null +++ b/webkit/glue/plugins/npapi_extension_thunk.h @@ -0,0 +1,23 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ +#define WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ + +#include "third_party/npapi/bindings/npapi_extensions.h" + +// This file implements forwarding for the NPAPI "Pepper" extensions through to +// the WebPluginDelegate associated with the plugin. + +namespace NPAPI { + +// Implements NPN_GetValue for the case of NPNVPepperExtensions. The function +// pointers in the returned structure implement all the extensions. +NPError GetPepperExtensionsFunctions(void* value); + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_NPAPI_EXTENSION_THUNK_H_ + + diff --git a/webkit/glue/plugins/pepper_buffer.cc b/webkit/glue/plugins/pepper_buffer.cc new file mode 100644 index 0000000..1c0bdd8 --- /dev/null +++ b/webkit/glue/plugins/pepper_buffer.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_buffer.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/pp_instance.h" +#include "third_party/ppapi/c/pp_module.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/ppb_buffer.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Module module_id, int32_t size) { + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return NULL; + + scoped_refptr<Buffer> buffer(new Buffer(module)); + if (!buffer->Init(size)) + return NULL; + + return buffer->GetReference(); +} + +bool IsBuffer(PP_Resource resource) { + return !!Resource::GetAs<Buffer>(resource); +} + +bool Describe(PP_Resource resource, int32_t* size_in_bytes) { + scoped_refptr<Buffer> buffer(Resource::GetAs<Buffer>(resource)); + if (!buffer) + return false; + buffer->Describe(size_in_bytes); + return true; +} + +void* Map(PP_Resource resource) { + scoped_refptr<Buffer> buffer(Resource::GetAs<Buffer>(resource)); + if (!buffer) + return NULL; + return buffer->Map(); +} + +void Unmap(PP_Resource resource) { + scoped_refptr<Buffer> buffer(Resource::GetAs<Buffer>(resource)); + if (!buffer) + return; + return buffer->Unmap(); +} + +const PPB_Buffer ppb_buffer = { + &Create, + &IsBuffer, + &Describe, + &Map, + &Unmap, +}; + +} // namespace + +Buffer::Buffer(PluginModule* module) + : Resource(module), + size_(0) { +} + +Buffer::~Buffer() { +} + +// static +const PPB_Buffer* Buffer::GetInterface() { + return &ppb_buffer; +} + +bool Buffer::Init(int size) { + if (size == 0) + return false; + Unmap(); + size_ = size; + return true; +} + +void Buffer::Describe(int* size_in_bytes) const { + *size_in_bytes = size_; +} + +void* Buffer::Map() { + if (size_ == 0) + return NULL; + + if (!is_mapped()) { + mem_buffer_.reset(new unsigned char[size_]); + memset(mem_buffer_.get(), 0, size_); + } + return mem_buffer_.get(); +} + +void Buffer::Unmap() { + mem_buffer_.reset(); +} + +void Buffer::Swap(Buffer* other) { + swap(other->mem_buffer_, mem_buffer_); + std::swap(other->size_, size_); +} + +} // namespace pepper + diff --git a/webkit/glue/plugins/pepper_buffer.h b/webkit/glue/plugins/pepper_buffer.h new file mode 100644 index 0000000..5d750ec --- /dev/null +++ b/webkit/glue/plugins/pepper_buffer.h @@ -0,0 +1,55 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_BUFFER_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_BUFFER_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/ppb_buffer.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace pepper { + +class PluginInstance; + +class Buffer : public Resource { + public: + explicit Buffer(PluginModule* module); + virtual ~Buffer(); + + int size() const { return size_; } + unsigned char* mapped_buffer() { return mem_buffer_.get(); } + + // Returns true if this buffer is mapped. False means that the buffer is + // either invalid or not mapped. + bool is_mapped() const { return !!mem_buffer_.get(); } + + // Returns a pointer to the interface implementing PPB_Buffer that is + // exposed to the plugin. + static const PPB_Buffer* GetInterface(); + + // Resource overrides. + Buffer* AsBuffer() { return this; } + + // PPB_Buffer implementation. + bool Init(int size); + void Describe(int* size_in_bytes) const; + void* Map(); + void Unmap(); + + // Swaps the guts of this buffer with another. + void Swap(Buffer* other); + + private: + int size_; + scoped_array<unsigned char> mem_buffer_; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_BUFFER_H_ + diff --git a/webkit/glue/plugins/pepper_device_context_2d.cc b/webkit/glue/plugins/pepper_device_context_2d.cc new file mode 100644 index 0000000..45ed9ee --- /dev/null +++ b/webkit/glue/plugins/pepper_device_context_2d.cc @@ -0,0 +1,553 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_device_context_2d.h" + +#include <iterator> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "gfx/blit.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "third_party/ppapi/c/pp_module.h" +#include "third_party/ppapi/c/pp_rect.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/ppb_device_context_2d.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" + +#if defined(OS_MACOSX) +#include "base/mac_util.h" +#include "base/scoped_cftyperef.h" +#endif + +namespace pepper { + +namespace { + +// Converts a rect inside an image of the given dimensions. The rect may be +// NULL to indicate it should be the entire image. If the rect is outside of +// the image, this will do nothing and return false. +bool ValidateAndConvertRect(const PP_Rect* rect, + int image_width, int image_height, + gfx::Rect* dest) { + if (!rect) { + // Use the entire image area. + *dest = gfx::Rect(0, 0, image_width, image_height); + } else { + // Validate the passed-in area. + if (rect->point.x < 0 || rect->point.y < 0 || + rect->size.width <= 0 || rect->size.height <= 0) + return false; + + // Check the max bounds, being careful of overflow. + if (static_cast<int64>(rect->point.x) + + static_cast<int64>(rect->size.width) > + static_cast<int64>(image_width)) + return false; + if (static_cast<int64>(rect->point.y) + + static_cast<int64>(rect->size.height) > + static_cast<int64>(image_height)) + return false; + + *dest = gfx::Rect(rect->point.x, rect->point.y, + rect->size.width, rect->size.height); + } + return true; +} + +PP_Resource Create(PP_Module module_id, + const PP_Size* size, + bool is_always_opaque) { + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return NULL; + + scoped_refptr<DeviceContext2D> context(new DeviceContext2D(module)); + if (!context->Init(size->width, size->height, is_always_opaque)) + return NULL; + return context->GetReference(); +} + +bool IsDeviceContext2D(PP_Resource resource) { + return !!Resource::GetAs<DeviceContext2D>(resource); +} + +bool Describe(PP_Resource device_context, + PP_Size* size, + bool* is_always_opaque) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context)); + if (!context) + return false; + return context->Describe(size, is_always_opaque); +} + +bool PaintImageData(PP_Resource device_context, + PP_Resource image, + const PP_Point* top_left, + const PP_Rect* src_rect) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context)); + if (!context) + return false; + return context->PaintImageData(image, top_left, src_rect); +} + +bool Scroll(PP_Resource device_context, + const PP_Rect* clip_rect, + const PP_Point* amount) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context)); + if (!context) + return false; + return context->Scroll(clip_rect, amount); +} + +bool ReplaceContents(PP_Resource device_context, PP_Resource image) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context)); + if (!context) + return false; + return context->ReplaceContents(image); +} + +int32_t Flush(PP_Resource device_context, + PP_CompletionCallback callback) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context)); + if (!context) + return PP_ERROR_BADRESOURCE; + return context->Flush(callback); +} + +const PPB_DeviceContext2D ppb_devicecontext2d = { + &Create, + &IsDeviceContext2D, + &Describe, + &PaintImageData, + &Scroll, + &ReplaceContents, + &Flush +}; + +} // namespace + +struct DeviceContext2D::QueuedOperation { + enum Type { + PAINT, + SCROLL, + REPLACE + }; + + QueuedOperation(Type t) + : type(t), + paint_x(0), + paint_y(0), + scroll_dx(0), + scroll_dy(0) { + } + + Type type; + + // Valid when type == PAINT. + scoped_refptr<ImageData> paint_image; + int paint_x, paint_y; + gfx::Rect paint_src_rect; + + // Valid when type == SCROLL. + gfx::Rect scroll_clip_rect; + int scroll_dx, scroll_dy; + + // Valid when type == REPLACE. + scoped_refptr<ImageData> replace_image; +}; + +DeviceContext2D::DeviceContext2D(PluginModule* module) + : Resource(module), + bound_instance_(NULL), + flushed_any_data_(false), + offscreen_flush_pending_(false) { +} + +DeviceContext2D::~DeviceContext2D() { +} + +// static +const PPB_DeviceContext2D* DeviceContext2D::GetInterface() { + return &ppb_devicecontext2d; +} + +bool DeviceContext2D::Init(int width, int height, bool is_always_opaque) { + // The underlying ImageData will validate the dimensions. + image_data_ = new ImageData(module()); + if (!image_data_->Init(PP_IMAGEDATAFORMAT_BGRA_PREMUL, width, height, true) || + !image_data_->Map()) { + image_data_ = NULL; + return false; + } + + return true; +} + +bool DeviceContext2D::Describe(PP_Size* size, bool* is_always_opaque) { + size->width = image_data_->width(); + size->height = image_data_->height(); + *is_always_opaque = false; // TODO(brettw) implement this. + return true; +} + +bool DeviceContext2D::PaintImageData(PP_Resource image, + const PP_Point* top_left, + const PP_Rect* src_rect) { + if (!top_left) + return false; + + scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); + if (!image_resource) + return false; + + QueuedOperation operation(QueuedOperation::PAINT); + operation.paint_image = image_resource; + if (!ValidateAndConvertRect(src_rect, image_resource->width(), + image_resource->height(), + &operation.paint_src_rect)) + return false; + + // Validate the bitmap position using the previously-validated rect, there + // should be no painted area outside of the image. + int64 x64 = static_cast<int64>(top_left->x); + int64 y64 = static_cast<int64>(top_left->y); + if (x64 + static_cast<int64>(operation.paint_src_rect.x()) < 0 || + x64 + static_cast<int64>(operation.paint_src_rect.right()) > + image_data_->width()) + return false; + if (y64 + static_cast<int64>(operation.paint_src_rect.y()) < 0 || + y64 + static_cast<int64>(operation.paint_src_rect.bottom()) > + image_data_->height()) + return false; + operation.paint_x = top_left->x; + operation.paint_y = top_left->y; + + queued_operations_.push_back(operation); + return true; +} + +bool DeviceContext2D::Scroll(const PP_Rect* clip_rect, + const PP_Point* amount) { + QueuedOperation operation(QueuedOperation::SCROLL); + if (!ValidateAndConvertRect(clip_rect, + image_data_->width(), + image_data_->height(), + &operation.scroll_clip_rect)) + return false; + + // If we're being asked to scroll by more than the clip rect size, just + // ignore this scroll command and say it worked. + int32 dx = amount->x; + int32 dy = amount->y; + if (dx <= -image_data_->width() || dx >= image_data_->width() || + dx <= -image_data_->height() || dy >= image_data_->height()) + return true; + + operation.scroll_dx = dx; + operation.scroll_dy = dy; + + queued_operations_.push_back(operation); + return false; +} + +bool DeviceContext2D::ReplaceContents(PP_Resource image) { + scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); + if (!image_resource) + return false; + if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL) + return false; + + if (image_resource->width() != image_data_->width() || + image_resource->height() != image_data_->height()) + return false; + + QueuedOperation operation(QueuedOperation::REPLACE); + operation.replace_image = image_resource; + queued_operations_.push_back(operation); + + return true; +} + +int32_t DeviceContext2D::Flush(const PP_CompletionCallback& callback) { + // Don't allow more than one pending flush at a time. + if (HasPendingFlush()) + return PP_ERROR_INPROGRESS; + + // TODO(brettw) check that the current thread is not the main one and + // implement blocking flushes in this case. + if (!callback.func) + return PP_ERROR_BADARGUMENT; + + gfx::Rect changed_rect; + for (size_t i = 0; i < queued_operations_.size(); i++) { + QueuedOperation& operation = queued_operations_[i]; + gfx::Rect op_rect; + switch (operation.type) { + case QueuedOperation::PAINT: + ExecutePaintImageData(operation.paint_image, + operation.paint_x, operation.paint_y, + operation.paint_src_rect, + &op_rect); + break; + case QueuedOperation::SCROLL: + ExecuteScroll(operation.scroll_clip_rect, + operation.scroll_dx, operation.scroll_dy, + &op_rect); + break; + case QueuedOperation::REPLACE: + ExecuteReplaceContents(operation.replace_image, &op_rect); + break; + } + changed_rect = changed_rect.Union(op_rect); + } + queued_operations_.clear(); + flushed_any_data_ = true; + + // We need the rect to be in terms of the current clip rect of the plugin + // since that's what will actually be painted. If we issue an invalidate + // for a clipped-out region, WebKit will do nothing and we won't get any + // ViewInitiatedPaint/ViewFlushedPaint calls, leaving our callback stranded. + gfx::Rect visible_changed_rect; + if (bound_instance_ && !changed_rect.IsEmpty()) + visible_changed_rect = bound_instance_->clip().Intersect(changed_rect); + + if (bound_instance_ && !visible_changed_rect.IsEmpty()) { + unpainted_flush_callback_.Set(callback); + bound_instance_->InvalidateRect(visible_changed_rect); + } else { + // There's nothing visible to invalidate so just schedule the callback to + // execute in the next round of the message loop. + ScheduleOffscreenCallback(FlushCallbackData(callback)); + } + return PP_ERROR_WOULDBLOCK; +} + +bool DeviceContext2D::ReadImageData(PP_Resource image, + const PP_Point* top_left) { + // Get and validate the image object to paint into. + scoped_refptr<ImageData> image_resource(Resource::GetAs<ImageData>(image)); + if (!image_resource) + return false; + if (image_resource->format() != PP_IMAGEDATAFORMAT_BGRA_PREMUL) + return false; // Must be in the right format. + + // Validate the bitmap position. + int x = top_left->x; + if (x < 0 || + static_cast<int64>(x) + static_cast<int64>(image_resource->width()) > + image_data_->width()) + return false; + int y = top_left->y; + if (y < 0 || + static_cast<int64>(y) + static_cast<int64>(image_resource->height()) > + image_data_->height()) + return false; + + ImageDataAutoMapper auto_mapper(image_resource); + if (!auto_mapper.is_valid()) + return false; + skia::PlatformCanvas* dest_canvas = image_resource->mapped_canvas(); + + SkIRect src_irect = { x, y, + x + image_resource->width(), + y + image_resource->height() }; + SkRect dest_rect = { SkIntToScalar(0), + SkIntToScalar(0), + SkIntToScalar(image_resource->width()), + SkIntToScalar(image_resource->height()) }; + + // We want to replace the contents of the bitmap rather than blend. + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + dest_canvas->drawBitmapRect(*image_data_->GetMappedBitmap(), + &src_irect, dest_rect, &paint); + return true; +} + +bool DeviceContext2D::BindToInstance(PluginInstance* new_instance) { + if (bound_instance_ == new_instance) + return true; // Rebinding the same device, nothing to do. + if (bound_instance_ && new_instance) + return false; // Can't change a bound device. + + if (!new_instance) { + // When the device is detached, we'll not get any more paint callbacks so + // we need to clear the list, but we still want to issue any pending + // callbacks to the plugin. + if (!unpainted_flush_callback_.is_null()) { + ScheduleOffscreenCallback(unpainted_flush_callback_); + unpainted_flush_callback_.Clear(); + } + if (!painted_flush_callback_.is_null()) { + ScheduleOffscreenCallback(painted_flush_callback_); + painted_flush_callback_.Clear(); + } + } else if (flushed_any_data_) { + // Only schedule a paint if this backing store has had any data flushed to + // it. This is an optimization. A "normal" plugin will first allocated a + // backing store, bind it, and then execute their normal painting and + // update loop. If binding a device always invalidated, it would mean we + // would get one paint for the bind, and one for the first time the plugin + // actually painted something. By not bothering to schedule an invalidate + // when an empty device is initially bound, we can save an extra paint for + // many plugins during the critical page initialization phase. + new_instance->InvalidateRect(gfx::Rect()); + } + + bound_instance_ = new_instance; + return true; +} + +void DeviceContext2D::Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& plugin_rect, + const gfx::Rect& paint_rect) { + // We're guaranteed to have a mapped canvas since we mapped it in Init(). + const SkBitmap& backing_bitmap = *image_data_->GetMappedBitmap(); + +#if defined(OS_MACOSX) + SkAutoLockPixels lock(backing_bitmap); + + scoped_cftyperef<CGDataProviderRef> data_provider( + CGDataProviderCreateWithData( + NULL, backing_bitmap.getAddr32(0, 0), + backing_bitmap.rowBytes() * backing_bitmap.height(), NULL)); + scoped_cftyperef<CGImageRef> image( + CGImageCreate( + backing_bitmap.width(), backing_bitmap.height(), + 8, 32, backing_bitmap.rowBytes(), + mac_util::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + data_provider, NULL, false, kCGRenderingIntentDefault)); + + // Flip the transform + CGContextSaveGState(canvas); + float window_height = static_cast<float>(CGBitmapContextGetHeight(canvas)); + CGContextTranslateCTM(canvas, 0, window_height); + CGContextScaleCTM(canvas, 1.0, -1.0); + + CGRect bounds; + bounds.origin.x = plugin_rect.origin().x(); + bounds.origin.y = window_height - plugin_rect.origin().y() - + backing_bitmap.height(); + bounds.size.width = backing_bitmap.width(); + bounds.size.height = backing_bitmap.height(); + + CGContextDrawImage(canvas, bounds, image); + CGContextRestoreGState(canvas); +#else + gfx::Point origin(plugin_rect.origin().x(), plugin_rect.origin().y()); + canvas->drawBitmap(backing_bitmap, + SkIntToScalar(plugin_rect.origin().x()), + SkIntToScalar(plugin_rect.origin().y())); +#endif +} + +void DeviceContext2D::ViewInitiatedPaint() { + // Move any "unpainted" callback to the painted state. See + // |unpainted_flush_callback_| in the header for more. + if (!unpainted_flush_callback_.is_null()) { + DCHECK(painted_flush_callback_.is_null()); + std::swap(painted_flush_callback_, unpainted_flush_callback_); + } +} + +void DeviceContext2D::ViewFlushedPaint() { + // Notify any "painted" callback. See |unpainted_flush_callback_| in the + // header for more. + if (!painted_flush_callback_.is_null()) { + // We must clear this variable before issuing the callback. It will be + // common for the plugin to issue another invalidate in response to a flush + // callback, and we don't want to think that a callback is already pending. + FlushCallbackData callback; + std::swap(callback, painted_flush_callback_); + callback.Execute(PP_OK); + } +} + +void DeviceContext2D::ExecutePaintImageData(ImageData* image, + int x, int y, + const gfx::Rect& src_rect, + gfx::Rect* invalidated_rect) { + // Ensure the source image is mapped to read from it. + ImageDataAutoMapper auto_mapper(image); + if (!auto_mapper.is_valid()) + return; + + // Portion within the source image to cut out. + SkIRect src_irect = { src_rect.x(), src_rect.y(), + src_rect.right(), src_rect.bottom() }; + + // Location within the backing store to copy to. + *invalidated_rect = src_rect; + invalidated_rect->Offset(x, y); + SkRect dest_rect = { SkIntToScalar(invalidated_rect->x()), + SkIntToScalar(invalidated_rect->y()), + SkIntToScalar(invalidated_rect->right()), + SkIntToScalar(invalidated_rect->bottom()) }; + + // We're guaranteed to have a mapped canvas since we mapped it in Init(). + skia::PlatformCanvas* backing_canvas = image_data_->mapped_canvas(); + + // We want to replace the contents of the bitmap rather than blend. + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + backing_canvas->drawBitmapRect(*image->GetMappedBitmap(), + &src_irect, dest_rect, &paint); +} + +void DeviceContext2D::ExecuteScroll(const gfx::Rect& clip, int dx, int dy, + gfx::Rect* invalidated_rect) { + gfx::ScrollCanvas(image_data_->mapped_canvas(), + clip, gfx::Point(dx, dy)); + *invalidated_rect = clip; +} + +void DeviceContext2D::ExecuteReplaceContents(ImageData* image, + gfx::Rect* invalidated_rect) { + image_data_->Swap(image); + *invalidated_rect = gfx::Rect(0, 0, + image_data_->width(), image_data_->height()); +} + +void DeviceContext2D::ScheduleOffscreenCallback( + const FlushCallbackData& callback) { + DCHECK(!HasPendingFlush()); + offscreen_flush_pending_ = true; + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &DeviceContext2D::ExecuteOffscreenCallback, + callback)); +} + +void DeviceContext2D::ExecuteOffscreenCallback(FlushCallbackData data) { + DCHECK(offscreen_flush_pending_); + + // We must clear this flag before issuing the callback. It will be + // common for the plugin to issue another invalidate in response to a flush + // callback, and we don't want to think that a callback is already pending. + offscreen_flush_pending_ = false; + data.Execute(PP_OK); +} + +bool DeviceContext2D::HasPendingFlush() const { + return !unpainted_flush_callback_.is_null() || + !painted_flush_callback_.is_null() || + offscreen_flush_pending_; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_device_context_2d.h b/webkit/glue/plugins/pepper_device_context_2d.h new file mode 100644 index 0000000..603bd52 --- /dev/null +++ b/webkit/glue/plugins/pepper_device_context_2d.h @@ -0,0 +1,175 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/ppb_device_context_2d.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _ppb_DeviceContext2D PPB_DeviceContext2D; + +namespace gfx { +class Rect; +} + +namespace pepper { + +class ImageData; +class PluginInstance; +class PluginModule; + +class DeviceContext2D : public Resource { + public: + DeviceContext2D(PluginModule* module); + virtual ~DeviceContext2D(); + + // Returns a pointer to the interface implementing PPB_ImageData that is + // exposed to the plugin. + static const PPB_DeviceContext2D* GetInterface(); + + bool Init(int width, int height, bool is_always_opaque); + + // Resource override. + virtual DeviceContext2D* AsDeviceContext2D() { return this; } + + // PPB_DeviceContext2D functions. + bool Describe(PP_Size* size, bool* is_always_opaque); + bool PaintImageData(PP_Resource image, + const PP_Point* top_left, + const PP_Rect* src_rect); + bool Scroll(const PP_Rect* clip_rect, const PP_Point* amount); + bool ReplaceContents(PP_Resource image); + int32_t Flush(const PP_CompletionCallback& callback); + + bool ReadImageData(PP_Resource image, const PP_Point* top_left); + + // Assciates this device with the given plugin instance. You can pass NULL to + // clear the existing device. Returns true on success. In this case, a + // repaint of the page will also be scheduled. Failure means that the device + // is already bound to a different instance, and nothing will happen. + bool BindToInstance(PluginInstance* new_instance); + + // Paints the current backing store to the web page. + void Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& plugin_rect, + const gfx::Rect& paint_rect); + + // Notifications that the view has rendered the page and that it has been + // flushed to the screen. These messages are used to send Flush callbacks to + // the plugin. See + void ViewInitiatedPaint(); + void ViewFlushedPaint(); + + ImageData* image_data() { return image_data_.get(); } + + private: + // Tracks a call to flush that requires a callback. + class FlushCallbackData { + public: + FlushCallbackData() { + Clear(); + } + + FlushCallbackData(const PP_CompletionCallback& callback) { + Set(callback); + } + + bool is_null() const { return !callback_.func; } + + void Set(const PP_CompletionCallback& callback) { + callback_ = callback; + } + + void Clear() { + callback_ = PP_MakeCompletionCallback(NULL, 0); + } + + void Execute(int32_t result) { + PP_RunCompletionCallback(&callback_, result); + } + + private: + PP_CompletionCallback callback_; + }; + + // Called internally to execute the different queued commands. The + // parameters to these functions will have already been validated. The last + // rect argument will be filled by each function with the area affected by + // the update that requires invalidation. If there were no pixels changed, + // this rect can be untouched. + void ExecutePaintImageData(ImageData* image, + int x, int y, + const gfx::Rect& src_rect, + gfx::Rect* invalidated_rect); + void ExecuteScroll(const gfx::Rect& clip, int dx, int dy, + gfx::Rect* invalidated_rect); + void ExecuteReplaceContents(ImageData* image, + gfx::Rect* invalidated_rect); + + // Schedules the offscreen callback to be fired at a future time. This + // will add the given item to the offscreen_flush_callbacks_ vector. + void ScheduleOffscreenCallback(const FlushCallbackData& callback); + + // Function scheduled to execute by ScheduleOffscreenCallback that actually + // issues the offscreen callbacks. + void ExecuteOffscreenCallback(FlushCallbackData data); + + // Returns true if there is any type of flush callback pending. + bool HasPendingFlush() const; + + scoped_refptr<ImageData> image_data_; + + // Non-owning pointer to the plugin instance this device context is currently + // bound to, if any. If the device context is currently unbound, this will + // be NULL. + PluginInstance* bound_instance_; + + // Keeps track of all drawing commands queued before a Flush call. + struct QueuedOperation; + typedef std::vector<QueuedOperation> OperationQueue; + OperationQueue queued_operations_; + + // Indicates whether any changes have been flushed to the backing store. + // This is initially false and is set to true at the first Flush() call. + bool flushed_any_data_; + + // The plugin can give us one "Flush" at a time. This flush will either be in + // the "unpainted" state (in which case unpainted_flush_callback_ will be + // non-NULL) or painted, in which case painted_flush_callback_ will be + // non-NULL). There can also be an offscreen callback which is handled + // separately (see offscreen_callback_pending_). Only one of these three + // things may be set at a time to enforce the "only one pending flush at a + // time" constraint. + // + // "Unpainted" ones are flush requests which have never been painted. These + // could have been done while the RenderView was already waiting for an ACK + // from a previous paint, so won't generate a new one yet. + // + // "Painted" ones are those flushes that have been painted by RenderView, but + // for which the ACK from the browser has not yet been received. + // + // When we get updates from a plugin with a callback, it is first added to + // the unpainted callbacks. When the renderer has initiated a paint, we'll + // move it to the painted callbacks list. When the renderer receives a flush, + // we'll execute the callback and remove it from the list. + FlushCallbackData unpainted_flush_callback_; + FlushCallbackData painted_flush_callback_; + + // When doing offscreen flushes, we issue a task that issues the callback + // later. This is set when one of those tasks is pending so that we can + // enforce the "only one pending flush at a time" constraint in the API. + bool offscreen_flush_pending_; + + DISALLOW_COPY_AND_ASSIGN(DeviceContext2D); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_DEVICE_CONTEXT_2D_H_ diff --git a/webkit/glue/plugins/pepper_directory_reader.cc b/webkit/glue/plugins/pepper_directory_reader.cc new file mode 100644 index 0000000..93f19ee --- /dev/null +++ b/webkit/glue/plugins/pepper_directory_reader.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_directory_reader.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Resource directory_ref_id) { + scoped_refptr<FileRef> directory_ref( + Resource::GetAs<FileRef>(directory_ref_id)); + if (!directory_ref) + return 0; + + DirectoryReader* reader = new DirectoryReader(directory_ref); + return reader->GetReference(); +} + +bool IsDirectoryReader(PP_Resource resource) { + return !!Resource::GetAs<DirectoryReader>(resource); +} + +int32_t GetNextEntry(PP_Resource reader_id, + PP_DirectoryEntry* entry, + PP_CompletionCallback callback) { + scoped_refptr<DirectoryReader> reader( + Resource::GetAs<DirectoryReader>(reader_id)); + if (!reader) + return PP_ERROR_BADRESOURCE; + + return reader->GetNextEntry(entry, callback); +} + +const PPB_DirectoryReader ppb_directoryreader = { + &Create, + &IsDirectoryReader, + &GetNextEntry +}; + +} // namespace + +DirectoryReader::DirectoryReader(FileRef* directory_ref) + : Resource(directory_ref->module()), + directory_ref_(directory_ref) { +} + +DirectoryReader::~DirectoryReader() { +} + +const PPB_DirectoryReader* DirectoryReader::GetInterface() { + return &ppb_directoryreader; +} + +int32_t DirectoryReader::GetNextEntry(PP_DirectoryEntry* entry, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_directory_reader.h b/webkit/glue/plugins/pepper_directory_reader.h new file mode 100644 index 0000000..c477a3e --- /dev/null +++ b/webkit/glue/plugins/pepper_directory_reader.h @@ -0,0 +1,37 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_DIRECTORY_READER_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_DIRECTORY_READER_H_ + +#include "third_party/ppapi/c/ppb_directory_reader.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace pepper { + +class FileRef; + +class DirectoryReader : public Resource { + public: + explicit DirectoryReader(FileRef* directory_ref); + virtual ~DirectoryReader(); + + // Returns a pointer to the interface implementing PPB_DirectoryReader that + // is exposed to the plugin. + static const PPB_DirectoryReader* GetInterface(); + + // Resource overrides. + DirectoryReader* AsDirectoryReader() { return this; } + + // PPB_DirectoryReader implementation. + int32_t GetNextEntry(PP_DirectoryEntry* entry, + PP_CompletionCallback callback); + + private: + scoped_refptr<FileRef> directory_ref_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_DIRECTORY_READER_H_ diff --git a/webkit/glue/plugins/pepper_event_conversion.cc b/webkit/glue/plugins/pepper_event_conversion.cc new file mode 100644 index 0000000..033ac93 --- /dev/null +++ b/webkit/glue/plugins/pepper_event_conversion.cc @@ -0,0 +1,235 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_event_conversion.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/pp_event.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" + +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; + +namespace { +// Anonymous namespace for functions converting WebInputEvent to PP_Event and +// back. +PP_Event_Type ConvertEventTypes(WebInputEvent::Type wetype) { + switch (wetype) { + case WebInputEvent::MouseDown: + return PP_EVENT_TYPE_MOUSEDOWN; + case WebInputEvent::MouseUp: + return PP_EVENT_TYPE_MOUSEUP; + case WebInputEvent::MouseMove: + return PP_EVENT_TYPE_MOUSEMOVE; + case WebInputEvent::MouseEnter: + return PP_EVENT_TYPE_MOUSEENTER; + case WebInputEvent::MouseLeave: + return PP_EVENT_TYPE_MOUSELEAVE; + case WebInputEvent::MouseWheel: + return PP_EVENT_TYPE_MOUSEWHEEL; + case WebInputEvent::RawKeyDown: + return PP_EVENT_TYPE_RAWKEYDOWN; + case WebInputEvent::KeyDown: + return PP_EVENT_TYPE_KEYDOWN; + case WebInputEvent::KeyUp: + return PP_EVENT_TYPE_KEYUP; + case WebInputEvent::Char: + return PP_EVENT_TYPE_CHAR; + case WebInputEvent::Undefined: + default: + return PP_EVENT_TYPE_UNDEFINED; + } +} + +void BuildKeyEvent(const WebInputEvent* event, PP_Event* pp_event) { + const WebKeyboardEvent* key_event = + reinterpret_cast<const WebKeyboardEvent*>(event); + pp_event->u.key.modifier = key_event->modifiers; + pp_event->u.key.normalizedKeyCode = key_event->windowsKeyCode; +} + +void BuildCharEvent(const WebInputEvent* event, PP_Event* pp_event) { + const WebKeyboardEvent* key_event = + reinterpret_cast<const WebKeyboardEvent*>(event); + pp_event->u.character.modifier = key_event->modifiers; + // For consistency, check that the sizes of the texts agree. + DCHECK(sizeof(pp_event->u.character.text) == sizeof(key_event->text)); + DCHECK(sizeof(pp_event->u.character.unmodifiedText) == + sizeof(key_event->unmodifiedText)); + for (size_t i = 0; i < WebKeyboardEvent::textLengthCap; ++i) { + pp_event->u.character.text[i] = key_event->text[i]; + pp_event->u.character.unmodifiedText[i] = key_event->unmodifiedText[i]; + } +} + +void BuildMouseEvent(const WebInputEvent* event, PP_Event* pp_event) { + const WebMouseEvent* mouse_event = + reinterpret_cast<const WebMouseEvent*>(event); + pp_event->u.mouse.modifier = mouse_event->modifiers; + pp_event->u.mouse.button = mouse_event->button; + pp_event->u.mouse.x = mouse_event->x; + pp_event->u.mouse.y = mouse_event->y; + pp_event->u.mouse.clickCount = mouse_event->clickCount; +} + +void BuildMouseWheelEvent(const WebInputEvent* event, PP_Event* pp_event) { + const WebMouseWheelEvent* mouse_wheel_event = + reinterpret_cast<const WebMouseWheelEvent*>(event); + pp_event->u.wheel.modifier = mouse_wheel_event->modifiers; + pp_event->u.wheel.deltaX = mouse_wheel_event->deltaX; + pp_event->u.wheel.deltaY = mouse_wheel_event->deltaY; + pp_event->u.wheel.wheelTicksX = mouse_wheel_event->wheelTicksX; + pp_event->u.wheel.wheelTicksY = mouse_wheel_event->wheelTicksY; + pp_event->u.wheel.scrollByPage = mouse_wheel_event->scrollByPage; +} + + +WebKeyboardEvent* BuildKeyEvent(const PP_Event& event) { + WebKeyboardEvent* key_event = new WebKeyboardEvent(); + switch (event.type) { + case PP_EVENT_TYPE_RAWKEYDOWN: + key_event->type = WebInputEvent::RawKeyDown; + break; + case PP_EVENT_TYPE_KEYDOWN: + key_event->type = WebInputEvent::KeyDown; + break; + case PP_EVENT_TYPE_KEYUP: + key_event->type = WebInputEvent::KeyUp; + break; + } + key_event->timeStampSeconds = event.time_stamp_seconds; + key_event->modifiers = event.u.key.modifier; + key_event->windowsKeyCode = event.u.key.normalizedKeyCode; + return key_event; +} + +WebKeyboardEvent* BuildCharEvent(const PP_Event& event) { + WebKeyboardEvent* key_event = new WebKeyboardEvent(); + key_event->type = WebInputEvent::Char; + key_event->timeStampSeconds = event.time_stamp_seconds; + key_event->modifiers = event.u.character.modifier; + // For consistency, check that the sizes of the texts agree. + DCHECK(sizeof(event.u.character.text) == sizeof(key_event->text)); + DCHECK(sizeof(event.u.character.unmodifiedText) == + sizeof(key_event->unmodifiedText)); + for (size_t i = 0; i < WebKeyboardEvent::textLengthCap; ++i) { + key_event->text[i] = event.u.character.text[i]; + key_event->unmodifiedText[i] = event.u.character.unmodifiedText[i]; + } + return key_event; +} + +WebMouseEvent* BuildMouseEvent(const PP_Event& event) { + WebMouseEvent* mouse_event = new WebMouseEvent(); + switch (event.type) { + case PP_EVENT_TYPE_MOUSEDOWN: + mouse_event->type = WebInputEvent::MouseDown; + break; + case PP_EVENT_TYPE_MOUSEUP: + mouse_event->type = WebInputEvent::MouseUp; + break; + case PP_EVENT_TYPE_MOUSEMOVE: + mouse_event->type = WebInputEvent::MouseMove; + break; + case PP_EVENT_TYPE_MOUSEENTER: + mouse_event->type = WebInputEvent::MouseEnter; + break; + case PP_EVENT_TYPE_MOUSELEAVE: + mouse_event->type = WebInputEvent::MouseLeave; + break; + } + mouse_event->timeStampSeconds = event.time_stamp_seconds; + mouse_event->modifiers = event.u.mouse.modifier; + mouse_event->button = + static_cast<WebMouseEvent::Button>(event.u.mouse.button); + mouse_event->x = event.u.mouse.x; + mouse_event->y = event.u.mouse.y; + mouse_event->clickCount = event.u.mouse.clickCount; + return mouse_event; +} + +WebMouseWheelEvent* BuildMouseWheelEvent(const PP_Event& event) { + WebMouseWheelEvent* mouse_wheel_event = new WebMouseWheelEvent(); + mouse_wheel_event->type = WebInputEvent::MouseWheel; + mouse_wheel_event->timeStampSeconds = event.time_stamp_seconds; + mouse_wheel_event->modifiers = event.u.wheel.modifier; + mouse_wheel_event->deltaX = event.u.wheel.deltaX; + mouse_wheel_event->deltaY = event.u.wheel.deltaY; + mouse_wheel_event->wheelTicksX = event.u.wheel.wheelTicksX; + mouse_wheel_event->wheelTicksY = event.u.wheel.wheelTicksY; + mouse_wheel_event->scrollByPage = event.u.wheel.scrollByPage; + return mouse_wheel_event; +} + +} // namespace + +namespace pepper { + +PP_Event* CreatePP_Event(const WebInputEvent& event) { + scoped_ptr<PP_Event> pp_event(new PP_Event); + + pp_event->type = ConvertEventTypes(event.type); + pp_event->size = sizeof(pp_event); + pp_event->time_stamp_seconds = event.timeStampSeconds; + switch (pp_event->type) { + case PP_EVENT_TYPE_UNDEFINED: + return NULL; + case PP_EVENT_TYPE_MOUSEDOWN: + case PP_EVENT_TYPE_MOUSEUP: + case PP_EVENT_TYPE_MOUSEMOVE: + case PP_EVENT_TYPE_MOUSEENTER: + case PP_EVENT_TYPE_MOUSELEAVE: + BuildMouseEvent(&event, pp_event.get()); + break; + case PP_EVENT_TYPE_MOUSEWHEEL: + BuildMouseWheelEvent(&event, pp_event.get()); + break; + case PP_EVENT_TYPE_RAWKEYDOWN: + case PP_EVENT_TYPE_KEYDOWN: + case PP_EVENT_TYPE_KEYUP: + BuildKeyEvent(&event, pp_event.get()); + break; + case PP_EVENT_TYPE_CHAR: + BuildCharEvent(&event, pp_event.get()); + break; + } + + return pp_event.release(); +} + +WebInputEvent* CreateWebInputEvent(const PP_Event& event) { + scoped_ptr<WebInputEvent> web_input_event; + switch (event.type) { + case PP_EVENT_TYPE_UNDEFINED: + return NULL; + case PP_EVENT_TYPE_MOUSEDOWN: + case PP_EVENT_TYPE_MOUSEUP: + case PP_EVENT_TYPE_MOUSEMOVE: + case PP_EVENT_TYPE_MOUSEENTER: + case PP_EVENT_TYPE_MOUSELEAVE: + web_input_event.reset(BuildMouseEvent(event)); + break; + case PP_EVENT_TYPE_MOUSEWHEEL: + web_input_event.reset(BuildMouseWheelEvent(event)); + break; + case PP_EVENT_TYPE_RAWKEYDOWN: + case PP_EVENT_TYPE_KEYDOWN: + case PP_EVENT_TYPE_KEYUP: + web_input_event.reset(BuildKeyEvent(event)); + break; + case PP_EVENT_TYPE_CHAR: + web_input_event.reset(BuildCharEvent(event)); + break; + case PP_EVENT_TYPE_FOCUS: + // NOTIMPLEMENTED(); + return NULL; + } + + return web_input_event.release(); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_event_conversion.h b/webkit/glue/plugins/pepper_event_conversion.h new file mode 100644 index 0000000..2d699cd --- /dev/null +++ b/webkit/glue/plugins/pepper_event_conversion.h @@ -0,0 +1,26 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_EVENT_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_EVENT_H_ + +typedef struct _pp_Event PP_Event; + +namespace WebKit { +class WebInputEvent; +} + +namespace pepper { + +// Creates a PP_Event from the given WebInputEvent. If it fails, returns NULL. +// The caller owns the created object on success. +PP_Event* CreatePP_Event(const WebKit::WebInputEvent& event); + +// Creates a WebInputEvent from the given PP_Event. If it fails, returns NULL. +// The caller owns the created object on success. +WebKit::WebInputEvent* CreateWebInputEvent(const PP_Event& event); + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_EVENT_H_ diff --git a/webkit/glue/plugins/pepper_file_chooser.cc b/webkit/glue/plugins/pepper_file_chooser.cc new file mode 100644 index 0000000..5e45600 --- /dev/null +++ b/webkit/glue/plugins/pepper_file_chooser.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_file_chooser.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Instance instance_id, + const PP_FileChooserOptions* options) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return 0; + + FileChooser* chooser = new FileChooser(instance, options); + return chooser->GetReference(); +} + +bool IsFileChooser(PP_Resource resource) { + return !!Resource::GetAs<FileChooser>(resource); +} + +int32_t Show(PP_Resource chooser_id, PP_CompletionCallback callback) { + scoped_refptr<FileChooser> chooser( + Resource::GetAs<FileChooser>(chooser_id)); + if (!chooser) + return PP_ERROR_BADRESOURCE; + + return chooser->Show(callback); +} + +PP_Resource GetNextChosenFile(PP_Resource chooser_id) { + scoped_refptr<FileChooser> chooser( + Resource::GetAs<FileChooser>(chooser_id)); + if (!chooser) + return 0; + + scoped_refptr<FileRef> file_ref(chooser->GetNextChosenFile()); + if (!file_ref) + return 0; + + return file_ref->GetReference(); +} + +const PPB_FileChooser ppb_filechooser = { + &Create, + &IsFileChooser, + &Show, + &GetNextChosenFile +}; + +} // namespace + +FileChooser::FileChooser(PluginInstance* instance, + const PP_FileChooserOptions* options) + : Resource(instance->module()), + mode_(options->mode), + accept_mime_types_(options->accept_mime_types) { +} + +FileChooser::~FileChooser() { +} + +// static +const PPB_FileChooser* FileChooser::GetInterface() { + return &ppb_filechooser; +} + +int32_t FileChooser::Show(PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +scoped_refptr<FileRef> FileChooser::GetNextChosenFile() { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return NULL; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_file_chooser.h b/webkit/glue/plugins/pepper_file_chooser.h new file mode 100644 index 0000000..8474188 --- /dev/null +++ b/webkit/glue/plugins/pepper_file_chooser.h @@ -0,0 +1,40 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_FILE_CHOOSER_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_FILE_CHOOSER_H_ + +#include <string> + +#include "third_party/ppapi/c/ppb_file_chooser.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace pepper { + +class PluginInstance; + +class FileChooser : public Resource { + public: + FileChooser(PluginInstance* instance, const PP_FileChooserOptions* options); + virtual ~FileChooser(); + + // Returns a pointer to the interface implementing PPB_FileChooser that is + // exposed to the plugin. + static const PPB_FileChooser* GetInterface(); + + // Resource overrides. + FileChooser* AsFileChooser() { return this; } + + // PPB_FileChooser implementation. + int32_t Show(PP_CompletionCallback callback); + scoped_refptr<FileRef> GetNextChosenFile(); + + private: + PP_FileChooserMode mode_; + std::string accept_mime_types_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_FILE_CHOOSER_H_ diff --git a/webkit/glue/plugins/pepper_file_io.cc b/webkit/glue/plugins/pepper_file_io.cc new file mode 100644 index 0000000..46f7276 --- /dev/null +++ b/webkit/glue/plugins/pepper_file_io.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_file_io.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "third_party/ppapi/c/ppb_file_io.h" +#include "third_party/ppapi/c/ppb_file_io_trusted.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Module module_id) { + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return 0; + + FileIO* file_io = new FileIO(module); + return file_io->GetReference(); +} + +bool IsFileIO(PP_Resource resource) { + return !!Resource::GetAs<FileIO>(resource); +} + +int32_t Open(PP_Resource file_io_id, + PP_Resource file_ref_id, + int32_t open_flags, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return PP_ERROR_BADRESOURCE; + + return file_io->Open(file_ref, open_flags, callback); +} + +int32_t Query(PP_Resource file_io_id, + PP_FileInfo* info, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->Query(info, callback); +} + +int32_t Touch(PP_Resource file_io_id, + PP_Time last_access_time, + PP_Time last_modified_time, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->Touch(last_access_time, last_modified_time, callback); +} + +int32_t Read(PP_Resource file_io_id, + int64_t offset, + char* buffer, + int32_t bytes_to_read, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->Read(offset, buffer, bytes_to_read, callback); +} + +int32_t Write(PP_Resource file_io_id, + int64_t offset, + const char* buffer, + int32_t bytes_to_write, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->Write(offset, buffer, bytes_to_write, callback); +} + +int32_t SetLength(PP_Resource file_io_id, + int64_t length, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->SetLength(length, callback); +} + +int32_t Flush(PP_Resource file_io_id, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->Flush(callback); +} + +void Close(PP_Resource file_io_id) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return; + + file_io->Close(); +} + +const PPB_FileIO ppb_fileio = { + &Create, + &IsFileIO, + &Open, + &Query, + &Touch, + &Read, + &Write, + &SetLength, + &Flush, + &Close +}; + +int32_t GetOSFileDescriptor(PP_Resource file_io_id) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->GetOSFileDescriptor(); +} + +int32_t WillWrite(PP_Resource file_io_id, + int64_t offset, + int32_t bytes_to_write, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->WillWrite(offset, bytes_to_write, callback); +} + +int32_t WillSetLength(PP_Resource file_io_id, + int64_t length, + PP_CompletionCallback callback) { + scoped_refptr<FileIO> file_io(Resource::GetAs<FileIO>(file_io_id)); + if (!file_io) + return PP_ERROR_BADRESOURCE; + + return file_io->WillSetLength(length, callback); +} + +const PPB_FileIOTrusted ppb_fileiotrusted = { + &GetOSFileDescriptor, + &WillWrite, + &WillSetLength +}; + +} // namespace + +FileIO::FileIO(PluginModule* module) : Resource(module) { +} + +FileIO::~FileIO() { +} + +// static +const PPB_FileIO* FileIO::GetInterface() { + return &ppb_fileio; +} + +// static +const PPB_FileIOTrusted* FileIO::GetTrustedInterface() { + return &ppb_fileiotrusted; +} + +int32_t FileIO::Open(FileRef* file_ref, + int32_t open_flags, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::Query(PP_FileInfo* info, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::Touch(PP_Time last_access_time, + PP_Time last_modified_time, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::Read(int64_t offset, + char* buffer, + int32_t bytes_to_read, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::Write(int64_t offset, + const char* buffer, + int32_t bytes_to_write, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::SetLength(int64_t length, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::Flush(PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +void FileIO::Close() { + NOTIMPLEMENTED(); // TODO(darin): Implement me! +} + +int32_t FileIO::GetOSFileDescriptor() { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::WillWrite(int64_t offset, + int32_t bytes_to_write, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +int32_t FileIO::WillSetLength(int64_t length, + PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_ERROR_FAILED; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_file_io.h b/webkit/glue/plugins/pepper_file_io.h new file mode 100644 index 0000000..4af6f2b --- /dev/null +++ b/webkit/glue/plugins/pepper_file_io.h @@ -0,0 +1,69 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_FILE_IO_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_FILE_IO_H_ + +#include "third_party/ppapi/c/pp_time.h" +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _pp_CompletionCallback PP_CompletionCallback; +typedef struct _pp_FileInfo PP_FileInfo; +typedef struct _ppb_FileIO PPB_FileIO; +typedef struct _ppb_FileIOTrusted PPB_FileIOTrusted; + +namespace pepper { + +class PluginModule; + +class FileIO : public Resource { + public: + explicit FileIO(PluginModule* module); + virtual ~FileIO(); + + // Returns a pointer to the interface implementing PPB_FileIO that is exposed + // to the plugin. + static const PPB_FileIO* GetInterface(); + + // Returns a pointer to the interface implementing PPB_FileIOTrusted that is + // exposed to the plugin. + static const PPB_FileIOTrusted* GetTrustedInterface(); + + // Resource overrides. + FileIO* AsFileIO() { return this; } + + // PPB_FileIO implementation. + int32_t Open(FileRef* file_ref, + int32_t open_flags, + PP_CompletionCallback callback); + int32_t Query(PP_FileInfo* info, + PP_CompletionCallback callback); + int32_t Touch(PP_Time last_access_time, + PP_Time last_modified_time, + PP_CompletionCallback callback); + int32_t Read(int64_t offset, + char* buffer, + int32_t bytes_to_read, + PP_CompletionCallback callback); + int32_t Write(int64_t offset, + const char* buffer, + int32_t bytes_to_write, + PP_CompletionCallback callback); + int32_t SetLength(int64_t length, + PP_CompletionCallback callback); + int32_t Flush(PP_CompletionCallback callback); + void Close(); + + // PPB_FileIOTrusted implementation. + int32_t GetOSFileDescriptor(); + int32_t WillWrite(int64_t offset, + int32_t bytes_to_write, + PP_CompletionCallback callback); + int32_t WillSetLength(int64_t length, + PP_CompletionCallback callback); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_FILE_IO_H_ diff --git a/webkit/glue/plugins/pepper_file_ref.cc b/webkit/glue/plugins/pepper_file_ref.cc new file mode 100644 index 0000000..9b42cff --- /dev/null +++ b/webkit/glue/plugins/pepper_file_ref.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_file_ref.h" + +#include "base/string_util.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_var.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +namespace { + +bool IsValidLocalPath(const std::string& path) { + // The path must start with '/' + if (path.empty() || path[0] != '/') + return false; + + // The path must contain valid UTF-8 characters. + if (!IsStringUTF8(path)) + return false; + + return true; +} + +void TrimTrailingSlash(std::string* path) { + // If this path ends with a slash, then normalize it away unless path is the + // root path. + if (path->size() > 1 && path->at(path->size() - 1) == '/') + path->erase(path->size() - 1, 1); +} + +PP_Resource CreateFileRef(PP_Instance instance_id, + PP_FileSystemType fs_type, + const char* path) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return 0; + + std::string origin; // TODO(darin): Extract from PluginInstance. + + std::string validated_path(path); + if (!IsValidLocalPath(validated_path)) + return 0; + TrimTrailingSlash(&validated_path); + + FileRef* file_ref = new FileRef(instance->module(), + fs_type, + validated_path, + origin); + return file_ref->GetReference(); +} + +PP_Resource CreatePersistentFileRef(PP_Instance instance_id, const char* path) { + return CreateFileRef(instance_id, PP_FILESYSTEMTYPE_LOCALPERSISTENT, path); +} + +PP_Resource CreateTemporaryFileRef(PP_Instance instance_id, const char* path) { + return CreateFileRef(instance_id, PP_FILESYSTEMTYPE_LOCALTEMPORARY, path); +} + +bool IsFileRef(PP_Resource resource) { + return !!Resource::GetAs<FileRef>(resource); +} + +PP_FileSystemType GetFileSystemType(PP_Resource file_ref_id) { + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return PP_FILESYSTEMTYPE_EXTERNAL; + + return file_ref->file_system_type(); +} + +PP_Var GetName(PP_Resource file_ref_id) { + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return PP_MakeVoid(); + + return StringToPPVar(file_ref->GetName()); +} + +PP_Var GetPath(PP_Resource file_ref_id) { + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return PP_MakeVoid(); + + if (file_ref->file_system_type() == PP_FILESYSTEMTYPE_EXTERNAL) + return PP_MakeVoid(); + + return StringToPPVar(file_ref->path()); +} + +PP_Resource GetParent(PP_Resource file_ref_id) { + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return 0; + + if (file_ref->file_system_type() == PP_FILESYSTEMTYPE_EXTERNAL) + return 0; + + scoped_refptr<FileRef> parent_ref(file_ref->GetParent()); + if (!parent_ref) + return 0; + + return parent_ref->GetReference(); +} + +const PPB_FileRef ppb_fileref = { + &CreatePersistentFileRef, + &CreateTemporaryFileRef, + &IsFileRef, + &GetFileSystemType, + &GetName, + &GetPath, + &GetParent +}; + +} // namespace + +FileRef::FileRef(PluginModule* module, + PP_FileSystemType file_system_type, + const std::string& validated_path, + const std::string& origin) + : Resource(module), + fs_type_(file_system_type), + path_(validated_path), + origin_(origin) { + // TODO(darin): Need to initialize system_path_. +} + +FileRef::~FileRef() { +} + +// static +const PPB_FileRef* FileRef::GetInterface() { + return &ppb_fileref; +} + +std::string FileRef::GetName() const { + if (path_.size() == 1 && path_[0] == '/') + return path_; + + // There should always be a leading slash at least! + size_t pos = path_.rfind('/'); + DCHECK(pos != std::string::npos); + + return path_.substr(pos + 1); +} + +scoped_refptr<FileRef> FileRef::GetParent() { + if (path_.size() == 1 && path_[0] == '/') + return this; + + // There should always be a leading slash at least! + size_t pos = path_.rfind('/'); + DCHECK(pos != std::string::npos); + + // If the path is "/foo", then we want to include the slash. + if (pos == 0) + pos++; + std::string parent_path = path_.substr(0, pos); + + FileRef* parent_ref = new FileRef(module(), fs_type_, parent_path, origin_); + return parent_ref; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_file_ref.h b/webkit/glue/plugins/pepper_file_ref.h new file mode 100644 index 0000000..34e4c3e --- /dev/null +++ b/webkit/glue/plugins/pepper_file_ref.h @@ -0,0 +1,55 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_FILE_REF_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_FILE_REF_H_ + +#include <string> + +#include "base/file_path.h" +#include "third_party/ppapi/c/ppb_file_ref.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace pepper { + +class PluginModule; + +class FileRef : public Resource { + public: + FileRef(PluginModule* module, + PP_FileSystemType file_system_type, + const std::string& validated_path, + const std::string& origin); + virtual ~FileRef(); + + // Returns a pointer to the interface implementing PPB_FileRef that is + // exposed to the plugin. + static const PPB_FileRef* GetInterface(); + + // Resource overrides. + FileRef* AsFileRef() { return this; } + + // PPB_FileRef implementation. + std::string GetName() const; + scoped_refptr<FileRef> GetParent(); + + PP_FileSystemType file_system_type() const { return fs_type_; } + + // Returns the virtual path (i.e., the path that the pepper plugin sees) + // corresponding to this file. + const std::string& path() const { return path_; } + + // Returns the system path corresponding to this file. + const FilePath& system_path() const { return system_path_; } + + private: + FilePath system_path_; + PP_FileSystemType fs_type_; + std::string path_; // UTF-8 encoded. + std::string origin_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_FILE_REF_H_ diff --git a/webkit/glue/plugins/pepper_file_system.cc b/webkit/glue/plugins/pepper_file_system.cc new file mode 100644 index 0000000..678399e --- /dev/null +++ b/webkit/glue/plugins/pepper_file_system.cc @@ -0,0 +1,59 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_file_system.h" + +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "third_party/ppapi/c/ppb_file_system.h" + +namespace pepper { + +namespace { + +int32_t MakeDirectory(PP_Resource directory_ref, + bool make_ancestors, + PP_CompletionCallback callback) { + return PP_ERROR_FAILED; // TODO(darin): Implement me! +} + +int32_t Query(PP_Resource file_ref, + PP_FileInfo* info, + PP_CompletionCallback callback) { + return PP_ERROR_FAILED; // TODO(darin): Implement me! +} + +int32_t Touch(PP_Resource file_ref, + PP_Time last_access_time, + PP_Time last_modified_time, + PP_CompletionCallback callback) { + return PP_ERROR_FAILED; // TODO(darin): Implement me! +} + +int32_t Delete(PP_Resource file_ref, + PP_CompletionCallback callback) { + return PP_ERROR_FAILED; // TODO(darin): Implement me! +} + +int32_t Rename(PP_Resource file_ref, + PP_Resource new_file_ref, + PP_CompletionCallback callback) { + return PP_ERROR_FAILED; // TODO(darin): Implement me! +} + +const PPB_FileSystem ppb_filesystem = { + &MakeDirectory, + &Query, + &Touch, + &Delete, + &Rename +}; + +} // namespace + +const PPB_FileSystem* FileSystem::GetInterface() { + return &ppb_filesystem; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_file_system.h b/webkit/glue/plugins/pepper_file_system.h new file mode 100644 index 0000000..b8ad01a --- /dev/null +++ b/webkit/glue/plugins/pepper_file_system.h @@ -0,0 +1,21 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_FILE_SYSTEM_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_FILE_SYSTEM_H_ + +typedef struct _ppb_FileSystem PPB_FileSystem; + +namespace pepper { + +class FileSystem { + public: + // Returns a pointer to the interface implementing PPB_FileSystem that is + // exposed to the plugin. + static const PPB_FileSystem* GetInterface(); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_FILE_SYSTEM_H_ diff --git a/webkit/glue/plugins/pepper_font.cc b/webkit/glue/plugins/pepper_font.cc new file mode 100644 index 0000000..af4cb81 --- /dev/null +++ b/webkit/glue/plugins/pepper_font.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2010 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 "build/build_config.h" + +#include "webkit/glue/plugins/pepper_font.h" + +#if defined(OS_LINUX) +#include <unistd.h> +#endif + +#include "base/logging.h" +#include "third_party/ppapi/c/ppb_font.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/webkit_glue.h" + +namespace pepper { + +namespace { + +PP_Resource MatchFontWithFallback(PP_Module module_id, + const PP_FontDescription* description) { +#if defined(OS_LINUX) + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return NULL; + + int fd = webkit_glue::MatchFontWithFallback(description->face, + description->weight >= 700, + description->italic, + description->charset); + if (fd == -1) + return NULL; + + scoped_refptr<Font> font(new Font(module, fd)); + + return font->GetReference(); +#else + // For trusted pepper plugins, this is only needed in Linux since font loading + // on Windows and Mac works through the renderer sandbox. + return false; +#endif +} + +bool IsFont(PP_Resource resource) { + return !!Resource::GetAs<Font>(resource); +} + +bool GetFontTable(PP_Resource font_id, + uint32_t table, + void* output, + uint32_t* output_length) { + scoped_refptr<Font> font(Resource::GetAs<Font>(font_id)); + if (!font.get()) + return false; + + return font->GetFontTable(table, output, output_length); +} + +const PPB_Font ppb_font = { + &MatchFontWithFallback, + &IsFont, + &GetFontTable, +}; + +} // namespace + +Font::Font(PluginModule* module, int fd) + : Resource(module), + fd_(fd) { +} + +Font::~Font() { +#if defined (OS_LINUX) + close(fd_); +#endif +} + +// static +const PPB_Font* Font::GetInterface() { + return &ppb_font; +} + +bool Font::GetFontTable(uint32_t table, + void* output, + uint32_t* output_length) { +#if defined(OS_LINUX) + size_t temp_size = static_cast<size_t>(*output_length); + bool rv = webkit_glue::GetFontTable( + fd_, table, static_cast<uint8_t*>(output), &temp_size); + *output_length = static_cast<uint32_t>(temp_size); + return rv; +#else + return false; +#endif +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_font.h b/webkit/glue/plugins/pepper_font.h new file mode 100644 index 0000000..ad1abba --- /dev/null +++ b/webkit/glue/plugins/pepper_font.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_FONT_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_FONT_H_ + +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _ppb_Font PPB_Font; + +namespace pepper { + +class PluginInstance; + +class Font : public Resource { + public: + Font(PluginModule* module, int fd); + virtual ~Font(); + + // Returns a pointer to the interface implementing PPB_Font that is exposed to + // the plugin. + static const PPB_Font* GetInterface(); + + // Resource overrides. + Font* AsFont() { return this; } + + // PPB_Font implementation. + bool GetFontTable(uint32_t table, + void* output, + uint32_t* output_length); + + private: + int fd_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_FONT_H_ diff --git a/webkit/glue/plugins/pepper_image_data.cc b/webkit/glue/plugins/pepper_image_data.cc new file mode 100644 index 0000000..8288fe2 --- /dev/null +++ b/webkit/glue/plugins/pepper_image_data.cc @@ -0,0 +1,159 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_image_data.h" + +#include <algorithm> +#include <limits> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/ppapi/c/pp_instance.h" +#include "third_party/ppapi/c/pp_module.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/ppb_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" + +namespace pepper { + +namespace { + +PP_ImageDataFormat GetNativeImageDataFormat() { + return PP_IMAGEDATAFORMAT_BGRA_PREMUL; +} + +PP_Resource Create(PP_Module module_id, + PP_ImageDataFormat format, + const PP_Size* size, + bool init_to_zero) { + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return NULL; + + scoped_refptr<ImageData> data(new ImageData(module)); + if (!data->Init(format, size->width, size->height, init_to_zero)) + return NULL; + + return data->GetReference(); +} + +bool IsImageData(PP_Resource resource) { + return !!Resource::GetAs<ImageData>(resource); +} + +bool Describe(PP_Resource resource, PP_ImageDataDesc* desc) { + // Give predictable values on failure. + memset(desc, 0, sizeof(PP_ImageDataDesc)); + + scoped_refptr<ImageData> image_data(Resource::GetAs<ImageData>(resource)); + if (!image_data) + return false; + image_data->Describe(desc); + return true; +} + +void* Map(PP_Resource resource) { + scoped_refptr<ImageData> image_data(Resource::GetAs<ImageData>(resource)); + if (!image_data) + return NULL; + return image_data->Map(); +} + +void Unmap(PP_Resource resource) { + scoped_refptr<ImageData> image_data(Resource::GetAs<ImageData>(resource)); + if (image_data) + image_data->Unmap(); +} + +const PPB_ImageData ppb_imagedata = { + &GetNativeImageDataFormat, + &Create, + &IsImageData, + &Describe, + &Map, + &Unmap, +}; + +} // namespace + +ImageData::ImageData(PluginModule* module) + : Resource(module), + width_(0), + height_(0) { +} + +ImageData::~ImageData() { +} + +// static +const PPB_ImageData* ImageData::GetInterface() { + return &ppb_imagedata; +} + +bool ImageData::Init(PP_ImageDataFormat format, + int width, int height, + bool init_to_zero) { + // TODO(brettw) this should be called only on the main thread! + // TODO(brettw) use init_to_zero when we implement caching. + if (format != PP_IMAGEDATAFORMAT_BGRA_PREMUL) + return false; // Only support this one format for now. + if (width <= 0 || height <= 0) + return false; + if (static_cast<int64>(width) * static_cast<int64>(height) >= + std::numeric_limits<int32>::max()) + return false; // Prevent overflow of signed 32-bit ints. + + platform_image_.reset( + module()->GetSomeInstance()->delegate()->CreateImage2D(width, height)); + width_ = width; + height_ = height; + return !!platform_image_.get(); +} + +void ImageData::Describe(PP_ImageDataDesc* desc) const { + desc->format = PP_IMAGEDATAFORMAT_BGRA_PREMUL; + desc->size.width = width_; + desc->size.height = height_; + desc->stride = width_ * 4; +} + +void* ImageData::Map() { + if (!mapped_canvas_.get()) { + mapped_canvas_.reset(platform_image_->Map()); + if (!mapped_canvas_.get()) + return NULL; + } + const SkBitmap& bitmap = + mapped_canvas_->getTopPlatformDevice().accessBitmap(true); + + // Our platform bitmaps are set to opaque by default, which we don't want. + const_cast<SkBitmap&>(bitmap).setIsOpaque(false); + + bitmap.lockPixels(); + return bitmap.getAddr32(0, 0); +} + +void ImageData::Unmap() { + // This is currently unimplemented, which is OK. The data will just always + // be around once it's mapped. Chrome's TransportDIB isn't currently + // unmappable without freeing it, but this may be something we want to support + // in the future to save some memory. +} + +const SkBitmap* ImageData::GetMappedBitmap() const { + if (!mapped_canvas_.get()) + return NULL; + return &mapped_canvas_->getTopPlatformDevice().accessBitmap(false); +} + +void ImageData::Swap(ImageData* other) { + swap(other->platform_image_, platform_image_); + swap(other->mapped_canvas_, mapped_canvas_); + std::swap(other->width_, width_); + std::swap(other->height_, height_); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_image_data.h b/webkit/glue/plugins/pepper_image_data.h new file mode 100644 index 0000000..7652b80 --- /dev/null +++ b/webkit/glue/plugins/pepper_image_data.h @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/ppb_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_delegate.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace skia { +class PlatformCanvas; +} + +class SkBitmap; + +namespace pepper { + +class ImageData : public Resource { + public: + explicit ImageData(PluginModule* module); + virtual ~ImageData(); + + int width() const { return width_; } + int height() const { return height_; } + + // Returns the image format. Currently there is only one format so this + // always returns the same thing. But if you care about the formation, you + // should probably check this so when we support multiple formats, we can't + // forget to update your code. + PP_ImageDataFormat format() const { return PP_IMAGEDATAFORMAT_BGRA_PREMUL; } + + // Returns true if this image is mapped. False means that the image is either + // invalid or not mapped. See ImageDataAutoMapper below. + bool is_mapped() const { return !!mapped_canvas_.get(); } + + // Returns a pointer to the interface implementing PPB_ImageData that is + // exposed to the plugin. + static const PPB_ImageData* GetInterface(); + + // Resource overrides. + ImageData* AsImageData() { return this; } + + // PPB_ImageData implementation. + bool Init(PP_ImageDataFormat format, + int width, int height, + bool init_to_zero); + void Describe(PP_ImageDataDesc* desc) const; + void* Map(); + void Unmap(); + + // The mapped bitmap and canvas will be NULL if the image is not mapped. + skia::PlatformCanvas* mapped_canvas() const { return mapped_canvas_.get(); } + const SkBitmap* GetMappedBitmap() const; + + // Swaps the guts of this image data with another. + void Swap(ImageData* other); + + private: + // This will be NULL before initialization, and if this ImageData is + // swapped with another. + scoped_ptr<PluginDelegate::PlatformImage2D> platform_image_; + + // When the device is mapped, this is the image. Null when umapped. + scoped_ptr<skia::PlatformCanvas> mapped_canvas_; + + int width_; + int height_; + + DISALLOW_COPY_AND_ASSIGN(ImageData); +}; + +// Manages mapping an image resource if necessary. Use this to ensure the +// image is mapped. The destructor will put the image back into the previous +// state. You must check is_valid() to make sure the image was successfully +// mapped before using it. +// +// Example: +// ImageDataAutoMapper mapper(image_data); +// if (!mapper.is_valid()) +// return utter_failure; +// image_data->mapped_canvas()->blah(); // Guaranteed valid. +class ImageDataAutoMapper { + public: + ImageDataAutoMapper(ImageData* image_data) : image_data_(image_data) { + if (image_data_->is_mapped()) { + is_valid_ = true; + needs_unmap_ = false; + } else { + is_valid_ = needs_unmap_ = !!image_data_->Map(); + } + } + + ~ImageDataAutoMapper() { + if (needs_unmap_) + image_data_->Unmap(); + } + + // Check this to see if the image was successfully mapped. If this is false, + // the image could not be mapped and is unusable. + bool is_valid() const { return is_valid_; } + + private: + ImageData* image_data_; + bool is_valid_; + bool needs_unmap_; + + DISALLOW_COPY_AND_ASSIGN(ImageDataAutoMapper); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_IMAGE_DATA_H_ diff --git a/webkit/glue/plugins/pepper_plugin_delegate.h b/webkit/glue/plugins/pepper_plugin_delegate.h new file mode 100644 index 0000000..ffc9d52 --- /dev/null +++ b/webkit/glue/plugins/pepper_plugin_delegate.h @@ -0,0 +1,59 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_ + +#include "third_party/ppapi/c/pp_stdint.h" + +namespace skia { +class PlatformCanvas; +} + +namespace pepper { + +class PluginInstance; + +// Virtual interface that the browser implements to implement features for +// Pepper plugins. +class PluginDelegate { + public: + // Represents an image. This is to allow the browser layer to supply a correct + // image representation. In Chrome, this will be a TransportDIB. + class PlatformImage2D { + public: + virtual ~PlatformImage2D() {} + + // Caller will own the returned pointer, returns NULL on failure. + virtual skia::PlatformCanvas* Map() = 0; + + // Returns the platform-specific shared memory handle of the data backing + // this image. This is used by NativeClient to send the image to the + // out-of-process plugin. Returns 0 on failure. + virtual intptr_t GetSharedMemoryHandle() const = 0; + }; + + // Indicates that the given instance has been created. + virtual void InstanceCreated(pepper::PluginInstance* instance) = 0; + + // Indicates that the given instance is being destroyed. This is called from + // the destructor, so it's important that the instance is not dereferenced + // from this call. + virtual void InstanceDeleted(pepper::PluginInstance* instance) = 0; + + // The caller will own the pointer returned from this. + virtual PlatformImage2D* CreateImage2D(int width, int height) = 0; + + // Notifies that the number of find results has changed. + virtual void DidChangeNumberOfFindResults(int identifier, + int total, + bool final_result) = 0; + + // Notifies that the index of the currently selected item has been updated. + virtual void DidChangeSelectedFindResult(int identifier, int index) = 0; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_DELEGATE_H_ diff --git a/webkit/glue/plugins/pepper_plugin_instance.cc b/webkit/glue/plugins/pepper_plugin_instance.cc new file mode 100644 index 0000000..6a7bf54 --- /dev/null +++ b/webkit/glue/plugins/pepper_plugin_instance.cc @@ -0,0 +1,828 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_plugin_instance.h" + +#include "base/logging.h" +#include "base/histogram.h" +#if defined(OS_MACOSX) +#include "base/mac_util.h" +#include "base/scoped_cftyperef.h" +#endif +#include "base/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "gfx/rect.h" +#if defined(OS_WIN) +#include "gfx/codec/jpeg_codec.h" +#include "gfx/gdi_util.h" +#endif +#include "gfx/skia_util.h" +#include "printing/native_metafile.h" +#include "printing/units.h" +#include "skia/ext/vector_platform_device.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/ppapi/c/pp_instance.h" +#include "third_party/ppapi/c/pp_event.h" +#include "third_party/ppapi/c/pp_rect.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/pp_var.h" +#include "third_party/ppapi/c/ppb_core.h" +#include "third_party/ppapi/c/ppb_find.h" +#include "third_party/ppapi/c/ppb_instance.h" +#include "third_party/ppapi/c/ppp_find.h" +#include "third_party/ppapi/c/ppp_instance.h" +#include "third_party/ppapi/c/ppp_zoom.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "webkit/glue/plugins/pepper_buffer.h" +#include "webkit/glue/plugins/pepper_device_context_2d.h" +#include "webkit/glue/plugins/pepper_event_conversion.h" +#include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_delegate.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/plugins/pepper_string.h" +#include "webkit/glue/plugins/pepper_url_loader.h" +#include "webkit/glue/plugins/pepper_var.h" + +using WebKit::WebCanvas; +using WebKit::WebCursorInfo; +using WebKit::WebFrame; +using WebKit::WebInputEvent; +using WebKit::WebPluginContainer; + +namespace pepper { + +#if defined(OS_WIN) +// Exported by pdf.dll +typedef bool (*RenderPDFPageToDCProc)( + const unsigned char* pdf_buffer, int buffer_size, int page_number, HDC dc, + int dpi_x, int dpi_y, int bounds_origin_x, int bounds_origin_y, + int bounds_width, int bounds_height, bool fit_to_bounds, + bool stretch_to_bounds, bool keep_aspect_ratio, bool center_in_bounds); +#endif // defined(OS_WIN) + +namespace { + +#define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, np_name) \ + COMPILE_ASSERT(int(WebCursorInfo::webkit_name) == int(np_name), \ + mismatching_enums) + +COMPILE_ASSERT_MATCHING_ENUM(TypePointer, PP_CURSORTYPE_POINTER); +COMPILE_ASSERT_MATCHING_ENUM(TypeCross, PP_CURSORTYPE_CROSS); +COMPILE_ASSERT_MATCHING_ENUM(TypeHand, PP_CURSORTYPE_HAND); +COMPILE_ASSERT_MATCHING_ENUM(TypeIBeam, PP_CURSORTYPE_IBEAM); +COMPILE_ASSERT_MATCHING_ENUM(TypeWait, PP_CURSORTYPE_WAIT); +COMPILE_ASSERT_MATCHING_ENUM(TypeHelp, PP_CURSORTYPE_HELP); +COMPILE_ASSERT_MATCHING_ENUM(TypeEastResize, PP_CURSORTYPE_EASTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthResize, PP_CURSORTYPE_NORTHRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthEastResize, + PP_CURSORTYPE_NORTHEASTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthWestResize, + PP_CURSORTYPE_NORTHWESTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthResize, PP_CURSORTYPE_SOUTHRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthEastResize, + PP_CURSORTYPE_SOUTHEASTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthWestResize, + PP_CURSORTYPE_SOUTHWESTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeWestResize, PP_CURSORTYPE_WESTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthSouthResize, + PP_CURSORTYPE_NORTHSOUTHRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeEastWestResize, PP_CURSORTYPE_EASTWESTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthEastSouthWestResize, + PP_CURSORTYPE_NORTHEASTSOUTHWESTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthWestSouthEastResize, + PP_CURSORTYPE_NORTHWESTSOUTHEASTRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeColumnResize, PP_CURSORTYPE_COLUMNRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeRowResize, PP_CURSORTYPE_ROWRESIZE); +COMPILE_ASSERT_MATCHING_ENUM(TypeMiddlePanning, PP_CURSORTYPE_MIDDLEPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeEastPanning, PP_CURSORTYPE_EASTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthPanning, PP_CURSORTYPE_NORTHPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthEastPanning, + PP_CURSORTYPE_NORTHEASTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeNorthWestPanning, + PP_CURSORTYPE_NORTHWESTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthPanning, PP_CURSORTYPE_SOUTHPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthEastPanning, + PP_CURSORTYPE_SOUTHEASTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeSouthWestPanning, + PP_CURSORTYPE_SOUTHWESTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeWestPanning, PP_CURSORTYPE_WESTPANNING); +COMPILE_ASSERT_MATCHING_ENUM(TypeMove, PP_CURSORTYPE_MOVE); +COMPILE_ASSERT_MATCHING_ENUM(TypeVerticalText, PP_CURSORTYPE_VERTICALTEXT); +COMPILE_ASSERT_MATCHING_ENUM(TypeCell, PP_CURSORTYPE_CELL); +COMPILE_ASSERT_MATCHING_ENUM(TypeContextMenu, PP_CURSORTYPE_CONTEXTMENU); +COMPILE_ASSERT_MATCHING_ENUM(TypeAlias, PP_CURSORTYPE_ALIAS); +COMPILE_ASSERT_MATCHING_ENUM(TypeProgress, PP_CURSORTYPE_PROGRESS); +COMPILE_ASSERT_MATCHING_ENUM(TypeNoDrop, PP_CURSORTYPE_NODROP); +COMPILE_ASSERT_MATCHING_ENUM(TypeCopy, PP_CURSORTYPE_COPY); +COMPILE_ASSERT_MATCHING_ENUM(TypeNone, PP_CURSORTYPE_NONE); +COMPILE_ASSERT_MATCHING_ENUM(TypeNotAllowed, PP_CURSORTYPE_NOTALLOWED); +COMPILE_ASSERT_MATCHING_ENUM(TypeZoomIn, PP_CURSORTYPE_ZOOMIN); +COMPILE_ASSERT_MATCHING_ENUM(TypeZoomOut, PP_CURSORTYPE_ZOOMOUT); +COMPILE_ASSERT_MATCHING_ENUM(TypeCustom, PP_CURSORTYPE_CUSTOM); + +void RectToPPRect(const gfx::Rect& input, PP_Rect* output) { + *output = PP_MakeRectFromXYWH(input.x(), input.y(), + input.width(), input.height()); +} + +PP_Var GetWindowObject(PP_Instance instance_id) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return PP_MakeVoid(); + return instance->GetWindowObject(); +} + +PP_Var GetOwnerElementObject(PP_Instance instance_id) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return PP_MakeVoid(); + return instance->GetOwnerElementObject(); +} + +bool BindGraphicsDeviceContext(PP_Instance instance_id, PP_Resource device_id) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return false; + return instance->BindGraphicsDeviceContext(device_id); +} + +bool IsFullFrame(PP_Instance instance_id) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return false; + return instance->full_frame(); +} + +bool SetCursor(PP_Instance instance_id, + PP_CursorType type, + PP_Resource custom_image_id, + const PP_Point* hot_spot) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return false; + + scoped_refptr<ImageData> custom_image( + Resource::GetAs<ImageData>(custom_image_id)); + if (custom_image.get()) { + // TODO: implement custom cursors. + NOTIMPLEMENTED(); + return false; + } + + return instance->SetCursor(type); +} + +const PPB_Instance ppb_instance = { + &GetWindowObject, + &GetOwnerElementObject, + &BindGraphicsDeviceContext, + &IsFullFrame, + &SetCursor, +}; + +void NumberOfFindResultsChanged(PP_Instance instance_id, + int32_t total, + bool final_result) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return; + + DCHECK_NE(instance->find_identifier(), -1); + instance->delegate()->DidChangeNumberOfFindResults( + instance->find_identifier(), total, final_result); +} + +void SelectedFindResultChanged(PP_Instance instance_id, + int32_t index) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return; + + DCHECK_NE(instance->find_identifier(), -1); + instance->delegate()->DidChangeSelectedFindResult( + instance->find_identifier(), index); +} + +const PPB_Find ppb_find = { + &NumberOfFindResultsChanged, + &SelectedFindResultChanged, +}; + +} // namespace + +PluginInstance::PluginInstance(PluginDelegate* delegate, + PluginModule* module, + const PPP_Instance* instance_interface) + : delegate_(delegate), + module_(module), + instance_interface_(instance_interface), + container_(NULL), + full_frame_(false), + find_identifier_(-1), + plugin_find_interface_(NULL), + plugin_zoom_interface_(NULL), +#if defined (OS_LINUX) + num_pages_(0), + pdf_output_done_(false), +#endif // defined (OS_LINUX) + plugin_print_interface_(NULL) { + memset(¤t_print_settings_, 0, sizeof(current_print_settings_)); + DCHECK(delegate); + module_->InstanceCreated(this); + delegate_->InstanceCreated(this); +} + +PluginInstance::~PluginInstance() { + delegate_->InstanceDeleted(this); + module_->InstanceDeleted(this); +} + +// static +const PPB_Instance* PluginInstance::GetInterface() { + return &ppb_instance; +} + +// static +PluginInstance* PluginInstance::FromPPInstance(PP_Instance instance) { + return reinterpret_cast<PluginInstance*>(instance); +} + +// static +const PPB_Find* PluginInstance::GetFindInterface() { + return &ppb_find; +} + +PP_Instance PluginInstance::GetPPInstance() { + return reinterpret_cast<intptr_t>(this); +} + +void PluginInstance::Paint(WebCanvas* canvas, + const gfx::Rect& plugin_rect, + const gfx::Rect& paint_rect) { + if (device_context_2d_) + device_context_2d_->Paint(canvas, plugin_rect, paint_rect); +} + +void PluginInstance::InvalidateRect(const gfx::Rect& rect) { + if (!container_ || position_.IsEmpty()) + return; // Nothing to do. + if (rect.IsEmpty()) + container_->invalidate(); + else + container_->invalidateRect(rect); +} + +PP_Var PluginInstance::GetWindowObject() { + if (!container_) + return PP_MakeVoid(); + + WebFrame* frame = container_->element().document().frame(); + if (!frame) + return PP_MakeVoid(); + + return NPObjectToPPVar(frame->windowObject()); +} + +PP_Var PluginInstance::GetOwnerElementObject() { + if (!container_) + return PP_MakeVoid(); + + return NPObjectToPPVar(container_->scriptableObjectForElement()); +} + +bool PluginInstance::BindGraphicsDeviceContext(PP_Resource device_id) { + if (!device_id) { + // Special-case clearing the current device. + if (device_context_2d_) { + device_context_2d_->BindToInstance(NULL); + device_context_2d_ = NULL; + InvalidateRect(gfx::Rect()); + } + return true; + } + + scoped_refptr<DeviceContext2D> device_2d = + Resource::GetAs<DeviceContext2D>(device_id); + + if (device_2d) { + if (!device_2d->BindToInstance(this)) + return false; // Can't bind to more than one instance. + + // See http://crbug.com/49403: this can be further optimized by keeping the + // old device around and painting from it. + if (device_context_2d_.get()) { + // Start the new image with the content of the old image until the plugin + // repaints. + const SkBitmap* old_backing_bitmap = + device_context_2d_->image_data()->GetMappedBitmap(); + SkRect old_size = SkRect::MakeWH( + SkScalar(static_cast<float>(old_backing_bitmap->width())), + SkScalar(static_cast<float>(old_backing_bitmap->height()))); + + SkCanvas canvas(*device_2d->image_data()->GetMappedBitmap()); + canvas.drawBitmap(*old_backing_bitmap, 0, 0); + + // Fill in any extra space with white. + canvas.clipRect(old_size, SkRegion::kDifference_Op); + canvas.drawARGB(255, 255, 255, 255); + } + + device_context_2d_ = device_2d; + // BindToInstance will have invalidated the plugin if necessary. + } + + return true; +} + +bool PluginInstance::SetCursor(PP_CursorType type) { + cursor_.reset(new WebCursorInfo(static_cast<WebCursorInfo::Type>(type))); + return true; +} + +void PluginInstance::Delete() { + instance_interface_->Delete(GetPPInstance()); + + container_ = NULL; +} + +bool PluginInstance::Initialize(WebPluginContainer* container, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + bool full_frame) { + container_ = container; + full_frame_ = full_frame; + + if (!instance_interface_->New(GetPPInstance())) + return false; + + size_t argc = 0; + scoped_array<const char*> argn(new const char*[arg_names.size()]); + scoped_array<const char*> argv(new const char*[arg_names.size()]); + for (size_t i = 0; i < arg_names.size(); ++i) { + argn[argc] = arg_names[i].c_str(); + argv[argc] = arg_values[i].c_str(); + argc++; + } + + return instance_interface_->Initialize(GetPPInstance(), + argc, argn.get(), argv.get()); +} + +bool PluginInstance::HandleDocumentLoad(URLLoader* loader) { + Resource::ScopedResourceId resource(loader); + return instance_interface_->HandleDocumentLoad(GetPPInstance(), resource.id); +} + +bool PluginInstance::HandleInputEvent(const WebKit::WebInputEvent& event, + WebCursorInfo* cursor_info) { + scoped_ptr<PP_Event> pp_event(CreatePP_Event(event)); + if (!pp_event.get()) + return false; + + bool rv = instance_interface_->HandleEvent(GetPPInstance(), pp_event.get()); + if (cursor_.get()) + *cursor_info = *cursor_; + return rv; +} + +PP_Var PluginInstance::GetInstanceObject() { + return instance_interface_->GetInstanceObject(GetPPInstance()); +} + +void PluginInstance::ViewChanged(const gfx::Rect& position, + const gfx::Rect& clip) { + position_ = position; + if (clip.IsEmpty()) { + // WebKit can give weird (x,y) positions for empty clip rects (since the + // position technically doesn't matter). But we want to make these + // consistent since this is given to the plugin, so force everything to 0 + // in the "everything is clipped" case. + clip_ = gfx::Rect(); + } else { + clip_ = clip; + } + + PP_Rect pp_position, pp_clip; + RectToPPRect(position_, &pp_position); + RectToPPRect(clip_, &pp_clip); + instance_interface_->ViewChanged(GetPPInstance(), &pp_position, &pp_clip); +} + +void PluginInstance::ViewInitiatedPaint() { + if (device_context_2d_) + device_context_2d_->ViewInitiatedPaint(); +} + +void PluginInstance::ViewFlushedPaint() { + if (device_context_2d_) + device_context_2d_->ViewFlushedPaint(); +} + +string16 PluginInstance::GetSelectedText(bool html) { + PP_Var rv = instance_interface_->GetSelectedText(GetPPInstance(), html); + String* string = GetString(rv); + if (!string) + return string16(); + return UTF8ToUTF16(string->value()); +} + +void PluginInstance::Zoom(float factor, bool text_only) { + if (!LoadZoomInterface()) + return; + plugin_zoom_interface_->Zoom(GetPPInstance(), factor, text_only); +} + +bool PluginInstance::StartFind(const string16& search_text, + bool case_sensitive, + int identifier) { + if (!LoadFindInterface()) + return false; + find_identifier_ = identifier; + return plugin_find_interface_->StartFind( + GetPPInstance(), + UTF16ToUTF8(search_text.c_str()).c_str(), + case_sensitive); +} + +void PluginInstance::SelectFindResult(bool forward) { + if (LoadFindInterface()) + plugin_find_interface_->SelectFindResult(GetPPInstance(), forward); +} + +void PluginInstance::StopFind() { + if (!LoadFindInterface()) + return; + find_identifier_ = -1; + plugin_find_interface_->StopFind(GetPPInstance()); +} + +bool PluginInstance::LoadFindInterface() { + if (!plugin_find_interface_) { + plugin_find_interface_ = + reinterpret_cast<const PPP_Find*>(module_->GetPluginInterface( + PPP_FIND_INTERFACE)); + } + + return !!plugin_find_interface_; +} + +bool PluginInstance::LoadZoomInterface() { + if (!plugin_zoom_interface_) { + plugin_zoom_interface_ = + reinterpret_cast<const PPP_Zoom*>(module_->GetPluginInterface( + PPP_ZOOM_INTERFACE)); + } + + return !!plugin_zoom_interface_; +} + +bool PluginInstance::GetPreferredPrintOutputFormat( + PP_PrintOutputFormat* format) { + if (!plugin_print_interface_) { + plugin_print_interface_ = + reinterpret_cast<const PPP_Printing*>(module_->GetPluginInterface( + PPP_PRINTING_INTERFACE)); + } + if (!plugin_print_interface_) + return false; + uint32_t format_count = 0; + PP_PrintOutputFormat* supported_formats = + plugin_print_interface_->QuerySupportedFormats(GetPPInstance(), + &format_count); + if (!supported_formats) + return false; + + bool found_supported_format = false; + for (uint32_t index = 0; index < format_count; index++) { + if (supported_formats[index] == PP_PRINTOUTPUTFORMAT_PDF) { + // If we found PDF, we are done. + found_supported_format = true; + *format = PP_PRINTOUTPUTFORMAT_PDF; + break; + } else if (supported_formats[index] == PP_PRINTOUTPUTFORMAT_RASTER) { + // We found raster. Keep looking. + found_supported_format = true; + *format = PP_PRINTOUTPUTFORMAT_RASTER; + } + } + PluginModule::GetCore()->MemFree(supported_formats); + return found_supported_format; +} + +bool PluginInstance::SupportsPrintInterface() { + PP_PrintOutputFormat format; + return GetPreferredPrintOutputFormat(&format); +} + +int PluginInstance::PrintBegin(const gfx::Rect& printable_area, + int printer_dpi) { + PP_PrintOutputFormat format; + if (!GetPreferredPrintOutputFormat(&format)) { + // PrintBegin should not have been called since SupportsPrintInterface + // would have returned false; + NOTREACHED(); + return 0; + } + + PP_PrintSettings print_settings; + RectToPPRect(printable_area, &print_settings.printable_area); + print_settings.dpi = printer_dpi; + print_settings.orientation = PP_PRINTORIENTATION_NORMAL; + print_settings.grayscale = false; + print_settings.format = format; + int num_pages = plugin_print_interface_->Begin(GetPPInstance(), + &print_settings); + if (!num_pages) + return 0; + current_print_settings_ = print_settings; +#if defined (OS_LINUX) + num_pages_ = num_pages; + pdf_output_done_ = false; +#endif // (OS_LINUX) + return num_pages; +} + +bool PluginInstance::PrintPage(int page_number, WebKit::WebCanvas* canvas) { + DCHECK(plugin_print_interface_); + PP_PrintPageNumberRange page_range; +#if defined(OS_LINUX) + if (current_print_settings_.format == PP_PRINTOUTPUTFORMAT_PDF) { + // On Linux we will try and output all pages as PDF in the first call to + // PrintPage. This is a temporary hack. + // TODO(sanjeevr): Remove this hack and fix this by changing the print + // interfaces for WebFrame and WebPlugin. + if (page_number != 0) + return pdf_output_done_; + page_range.first_page_number = 0; + page_range.last_page_number = num_pages_ - 1; + } +#else // defined(OS_LINUX) + page_range.first_page_number = page_range.last_page_number = page_number; +#endif // defined(OS_LINUX) + + PP_Resource print_output = + plugin_print_interface_->PrintPages(GetPPInstance(), &page_range, 1); + + if (!print_output) + return false; + + bool ret = false; + + if (current_print_settings_.format == PP_PRINTOUTPUTFORMAT_PDF) + ret = PrintPDFOutput(print_output, canvas); + else if (current_print_settings_.format == PP_PRINTOUTPUTFORMAT_RASTER) + ret = PrintRasterOutput(print_output, canvas); + + // Now we need to release the print output resource. + PluginModule::GetCore()->ReleaseResource(print_output); + + return ret; +} + +void PluginInstance::PrintEnd() { + DCHECK(plugin_print_interface_); + if (plugin_print_interface_) + plugin_print_interface_->End(GetPPInstance()); + memset(¤t_print_settings_, 0, sizeof(current_print_settings_)); +#if defined(OS_MACOSX) + last_printed_page_ = SkBitmap(); +#elif defined(OS_LINUX) + num_pages_ = 0; + pdf_output_done_ = false; +#endif // defined(OS_LINUX) +} + +bool PluginInstance::PrintPDFOutput(PP_Resource print_output, + WebKit::WebCanvas* canvas) { + scoped_refptr<Buffer> buffer(Resource::GetAs<Buffer>(print_output)); + if (!buffer.get() || !buffer->is_mapped() || !buffer->size()) { + NOTREACHED(); + return false; + } +#if defined(OS_WIN) + // For Windows, we need the PDF DLL to render the output PDF to a DC. + HMODULE pdf_module = GetModuleHandle(L"pdf.dll"); + if (!pdf_module) + return false; + RenderPDFPageToDCProc render_proc = + reinterpret_cast<RenderPDFPageToDCProc>( + GetProcAddress(pdf_module, "RenderPDFPageToDC")); + if (!render_proc) + return false; +#endif // defined(OS_WIN) + + bool ret = false; +#if defined(OS_LINUX) + // On Linux we need to get the backing PdfPsMetafile and write the bits + // directly. + cairo_t* context = canvas->beginPlatformPaint(); + printing::NativeMetafile* metafile = + printing::NativeMetafile::FromCairoContext(context); + DCHECK(metafile); + if (metafile) { + ret = metafile->SetRawData(buffer->mapped_buffer(), buffer->size()); + if (ret) + pdf_output_done_ = true; + } + canvas->endPlatformPaint(); +#elif defined(OS_MACOSX) + printing::NativeMetafile metafile; + // Create a PDF metafile and render from there into the passed in context. + if (metafile.Init(buffer->mapped_buffer(), buffer->size())) { + // Flip the transform. + CGContextSaveGState(canvas); + CGContextTranslateCTM(canvas, 0, + current_print_settings_.printable_area.size.height); + CGContextScaleCTM(canvas, 1.0, -1.0); + CGRect page_rect; + page_rect.origin.x = current_print_settings_.printable_area.point.x; + page_rect.origin.y = current_print_settings_.printable_area.point.y; + page_rect.size.width = current_print_settings_.printable_area.size.width; + page_rect.size.height = current_print_settings_.printable_area.size.height; + + ret = metafile.RenderPage(1, canvas, page_rect, true, false, true, true); + CGContextRestoreGState(canvas); + } +#elif defined(OS_WIN) + // On Windows, we now need to render the PDF to the DC that backs the + // supplied canvas. + skia::VectorPlatformDevice& device = + static_cast<skia::VectorPlatformDevice&>( + canvas->getTopPlatformDevice()); + HDC dc = device.getBitmapDC(); + gfx::Size size_in_pixels; + size_in_pixels.set_width( + printing::ConvertUnit(current_print_settings_.printable_area.size.width, + static_cast<int>(printing::kPointsPerInch), + current_print_settings_.dpi)); + size_in_pixels.set_height( + printing::ConvertUnit(current_print_settings_.printable_area.size.height, + static_cast<int>(printing::kPointsPerInch), + current_print_settings_.dpi)); + // We need to render using the actual printer DPI (rendering to a smaller + // set of pixels leads to a blurry output). However, we need to counter the + // scaling up that will happen in the browser. + XFORM xform = {0}; + xform.eM11 = xform.eM22 = static_cast<float>(printing::kPointsPerInch) / + static_cast<float>(current_print_settings_.dpi); + ModifyWorldTransform(dc, &xform, MWT_LEFTMULTIPLY); + + ret = render_proc(buffer->mapped_buffer(), buffer->size(), 0, dc, + current_print_settings_.dpi, current_print_settings_.dpi, + 0, 0, size_in_pixels.width(), + size_in_pixels.height(), true, false, true, true); +#endif // defined(OS_WIN) + + return ret; +} + +bool PluginInstance::PrintRasterOutput(PP_Resource print_output, + WebKit::WebCanvas* canvas) { + scoped_refptr<ImageData> image(Resource::GetAs<ImageData>(print_output)); + if (!image.get() || !image->is_mapped()) + return false; + + const SkBitmap* bitmap = image->GetMappedBitmap(); + if (!bitmap) + return false; + + // Draw the printed image into the supplied canvas. + SkIRect src_rect; + src_rect.set(0, 0, bitmap->width(), bitmap->height()); + SkRect dest_rect; + dest_rect.set( + SkIntToScalar(current_print_settings_.printable_area.point.x), + SkIntToScalar(current_print_settings_.printable_area.point.y), + SkIntToScalar(current_print_settings_.printable_area.point.x + + current_print_settings_.printable_area.size.width), + SkIntToScalar(current_print_settings_.printable_area.point.y + + current_print_settings_.printable_area.size.height)); + bool draw_to_canvas = true; + gfx::Rect dest_rect_gfx; + dest_rect_gfx.set_x(current_print_settings_.printable_area.point.x); + dest_rect_gfx.set_y(current_print_settings_.printable_area.point.y); + dest_rect_gfx.set_width(current_print_settings_.printable_area.size.width); + dest_rect_gfx.set_height(current_print_settings_.printable_area.size.height); + +#if defined(OS_WIN) + // Since this is a raster output, the size of the bitmap can be + // huge (especially at high printer DPIs). On Windows, this can + // result in a HUGE EMF (on Mac and Linux the output goes to PDF + // which appears to Flate compress the bitmap). So, if this bitmap + // is larger than 20 MB, we save the bitmap as a JPEG into the EMF + // DC. Note: We chose JPEG over PNG because JPEG compression seems + // way faster (about 4 times faster). + static const int kCompressionThreshold = 20 * 1024 * 1024; + if (bitmap->getSize() > kCompressionThreshold) { + DrawJPEGToPlatformDC(*bitmap, dest_rect_gfx, canvas); + draw_to_canvas = false; + } +#endif // defined(OS_WIN) +#if defined(OS_MACOSX) + draw_to_canvas = false; + DrawSkBitmapToCanvas(*bitmap, canvas, dest_rect_gfx, + current_print_settings_.printable_area.size.height); + // See comments in the header file. + last_printed_page_ = *bitmap; +#else // defined(OS_MACOSX) + if (draw_to_canvas) + canvas->drawBitmapRect(*bitmap, &src_rect, dest_rect); +#endif // defined(OS_MACOSX) + return true; +} + +#if defined(OS_WIN) +bool PluginInstance::DrawJPEGToPlatformDC( + const SkBitmap& bitmap, + const gfx::Rect& printable_area, + WebKit::WebCanvas* canvas) { + skia::VectorPlatformDevice& device = + static_cast<skia::VectorPlatformDevice&>( + canvas->getTopPlatformDevice()); + HDC dc = device.getBitmapDC(); + // TODO(sanjeevr): This is a temporary hack. If we output a JPEG + // to the EMF, the EnumEnhMetaFile call fails in the browser + // process. The failure also happens if we output nothing here. + // We need to investigate the reason for this failure and fix it. + // In the meantime this temporary hack of drawing an empty + // rectangle in the DC gets us by. + Rectangle(dc, 0, 0, 0, 0); + + // Ideally we should add JPEG compression to the VectorPlatformDevice class + // However, Skia currently has no JPEG compression code and we cannot + // depend on gfx/jpeg_codec.h in Skia. So we do the compression here. + SkAutoLockPixels lock(bitmap); + DCHECK(bitmap.getConfig() == SkBitmap::kARGB_8888_Config); + const uint32_t* pixels = + static_cast<const uint32_t*>(bitmap.getPixels()); + std::vector<unsigned char> compressed_image; + base::TimeTicks start_time = base::TimeTicks::Now(); + bool encoded = gfx::JPEGCodec::Encode( + reinterpret_cast<const unsigned char*>(pixels), + gfx::JPEGCodec::FORMAT_BGRA, bitmap.width(), bitmap.height(), + static_cast<int>(bitmap.rowBytes()), 100, &compressed_image); + UMA_HISTOGRAM_TIMES("PepperPluginPrint.RasterBitmapCompressTime", + base::TimeTicks::Now() - start_time); + if (!encoded) { + NOTREACHED(); + return false; + } + BITMAPINFOHEADER bmi = {0}; + gfx::CreateBitmapHeader(bitmap.width(), bitmap.height(), &bmi); + bmi.biCompression = BI_JPEG; + bmi.biSizeImage = compressed_image.size(); + bmi.biHeight = -bmi.biHeight; + StretchDIBits(dc, printable_area.x(), printable_area.y(), + printable_area.width(), printable_area.height(), + 0, 0, bitmap.width(), bitmap.height(), + &compressed_image.front(), + reinterpret_cast<const BITMAPINFO*>(&bmi), + DIB_RGB_COLORS, SRCCOPY); + return true; +} +#endif // OS_WIN + +#if defined(OS_MACOSX) +void PluginInstance::DrawSkBitmapToCanvas( + const SkBitmap& bitmap, WebKit::WebCanvas* canvas, + const gfx::Rect& dest_rect, + int canvas_height) { + SkAutoLockPixels lock(bitmap); + DCHECK(bitmap.getConfig() == SkBitmap::kARGB_8888_Config); + scoped_cftyperef<CGDataProviderRef> data_provider( + CGDataProviderCreateWithData( + NULL, bitmap.getAddr32(0, 0), + bitmap.rowBytes() * bitmap.height(), NULL)); + scoped_cftyperef<CGImageRef> image( + CGImageCreate( + bitmap.width(), bitmap.height(), + 8, 32, bitmap.rowBytes(), + mac_util::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + data_provider, NULL, false, kCGRenderingIntentDefault)); + + // Flip the transform + CGContextSaveGState(canvas); + CGContextTranslateCTM(canvas, 0, canvas_height); + CGContextScaleCTM(canvas, 1.0, -1.0); + + CGRect bounds; + bounds.origin.x = dest_rect.x(); + bounds.origin.y = canvas_height - dest_rect.y() - dest_rect.height(); + bounds.size.width = dest_rect.width(); + bounds.size.height = dest_rect.height(); + + CGContextDrawImage(canvas, bounds, image); + CGContextRestoreGState(canvas); +} +#endif // defined(OS_MACOSX) + + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_plugin_instance.h b/webkit/glue/plugins/pepper_plugin_instance.h new file mode 100644 index 0000000..4528a99 --- /dev/null +++ b/webkit/glue/plugins/pepper_plugin_instance.h @@ -0,0 +1,210 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "gfx/rect.h" +#include "third_party/ppapi/c/pp_cursor_type.h" +#include "third_party/ppapi/c/pp_instance.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/ppp_printing.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" + +typedef struct _pp_Var PP_Var; +typedef struct _ppb_Instance PPB_Instance; +typedef struct _ppb_Find PPB_Find; +typedef struct _ppp_Find PPP_Find; +typedef struct _ppp_Instance PPP_Instance; +typedef struct _ppp_Zoom PPP_Zoom; + +class SkBitmap; + +namespace gfx { +class Rect; +} + +namespace WebKit { +struct WebCursorInfo; +class WebInputEvent; +class WebPluginContainer; +} + +namespace pepper { + +class DeviceContext2D; +class PluginDelegate; +class PluginModule; +class URLLoader; + +class PluginInstance : public base::RefCounted<PluginInstance> { + public: + PluginInstance(PluginDelegate* delegate, + PluginModule* module, + const PPP_Instance* instance_interface); + ~PluginInstance(); + + static const PPB_Instance* GetInterface(); + + // Converts the given instance ID to an actual instance object. + static PluginInstance* FromPPInstance(PP_Instance instance); + + // Returns a pointer to the interface implementing PPB_Find that is + // exposed to the plugin. + static const PPB_Find* GetFindInterface(); + + PluginDelegate* delegate() const { return delegate_; } + PluginModule* module() const { return module_.get(); } + + WebKit::WebPluginContainer* container() const { return container_; } + + const gfx::Rect& position() const { return position_; } + const gfx::Rect& clip() const { return clip_; } + + int find_identifier() const { return find_identifier_; } + + PP_Instance GetPPInstance(); + + // Paints the current backing store to the web page. + void Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& plugin_rect, + const gfx::Rect& paint_rect); + + // Schedules a paint of the page for the given region. The coordinates are + // relative to the top-left of the plugin. This does nothing if the plugin + // has not yet been positioned. You can supply an empty gfx::Rect() to + // invalidate the entire plugin. + void InvalidateRect(const gfx::Rect& rect); + + // PPB_Instance implementation. + PP_Var GetWindowObject(); + PP_Var GetOwnerElementObject(); + bool BindGraphicsDeviceContext(PP_Resource device_id); + bool full_frame() const { return full_frame_; } + bool SetCursor(PP_CursorType type); + + // PPP_Instance pass-through. + void Delete(); + bool Initialize(WebKit::WebPluginContainer* container, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + bool full_frame); + bool HandleDocumentLoad(URLLoader* loader); + bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor_info); + PP_Var GetInstanceObject(); + void ViewChanged(const gfx::Rect& position, const gfx::Rect& clip); + + // Notifications that the view has rendered the page and that it has been + // flushed to the screen. These messages are used to send Flush callbacks to + // the plugin for DeviceContext2D. + void ViewInitiatedPaint(); + void ViewFlushedPaint(); + + string16 GetSelectedText(bool html); + void Zoom(float factor, bool text_only); + bool StartFind(const string16& search_text, + bool case_sensitive, + int identifier); + void SelectFindResult(bool forward); + void StopFind(); + + bool SupportsPrintInterface(); + int PrintBegin(const gfx::Rect& printable_area, int printer_dpi); + bool PrintPage(int page_number, WebKit::WebCanvas* canvas); + void PrintEnd(); + + private: + bool LoadFindInterface(); + bool LoadZoomInterface(); + + // Queries the plugin for supported print formats and sets |format| to the + // best format to use. Returns false if the plugin does not support any + // print format that we can handle (we can handle raster and PDF). + bool GetPreferredPrintOutputFormat(PP_PrintOutputFormat* format); + bool PrintPDFOutput(PP_Resource print_output, WebKit::WebCanvas* canvas); + bool PrintRasterOutput(PP_Resource print_output, WebKit::WebCanvas* canvas); +#if defined(OS_WIN) + bool DrawJPEGToPlatformDC(const SkBitmap& bitmap, + const gfx::Rect& printable_area, + WebKit::WebCanvas* canvas); +#elif defined(OS_MACOSX) + // Draws the given kARGB_8888_Config bitmap to the specified canvas starting + // at the specified destination rect. + void DrawSkBitmapToCanvas(const SkBitmap& bitmap, WebKit::WebCanvas* canvas, + const gfx::Rect& dest_rect, int canvas_height); +#endif // OS_MACOSX + + PluginDelegate* delegate_; + scoped_refptr<PluginModule> module_; + const PPP_Instance* instance_interface_; + + // NULL until we have been initialized. + WebKit::WebPluginContainer* container_; + + // Indicates whether this is a full frame instance, which means it represents + // an entire document rather than an embed tag. + bool full_frame_; + + // Position in the viewport (which moves as the page is scrolled) of this + // plugin. This will be a 0-sized rectangle if the plugin has not yet been + // laid out. + gfx::Rect position_; + + // Current clip rect. This will be empty if the plugin is not currently + // visible. This is in the plugin's coordinate system, so fully visible will + // be (0, 0, w, h) regardless of scroll position. + gfx::Rect clip_; + + // The current device context for painting in 2D. + scoped_refptr<DeviceContext2D> device_context_2d_; + + // The id of the current find operation, or -1 if none is in process. + int find_identifier_; + + // The plugin find and zoom interfaces. + const PPP_Find* plugin_find_interface_; + const PPP_Zoom* plugin_zoom_interface_; + + // This is only valid between a successful PrintBegin call and a PrintEnd + // call. + PP_PrintSettings current_print_settings_; +#if defined(OS_MACOSX) + // On the Mac, when we draw the bitmap to the PDFContext, it seems necessary + // to keep the pixels valid until CGContextEndPage is called. We use this + // variable to hold on to the pixels. + SkBitmap last_printed_page_; +#elif defined(OS_LINUX) + // On Linux, we always send all pages from the renderer to the browser. + // So, if the plugin supports printPagesAsPDF we print the entire output + // in one shot in the first call to PrintPage. + // (This is a temporary hack until we change the WebFrame and WebPlugin print + // interfaces). + // Specifies the total number of pages to be printed. It it set in PrintBegin. + int32 num_pages_; + // Specifies whether we have already output all pages. This is used to ignore + // subsequent PrintPage requests. + bool pdf_output_done_; +#endif // defined(OS_LINUX) + + // The plugin print interface. + const PPP_Printing* plugin_print_interface_; + + // Containes the cursor if it's set by the plugin. + scoped_ptr<WebKit::WebCursorInfo> cursor_; + + DISALLOW_COPY_AND_ASSIGN(PluginInstance); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_INSTANCE_H_ diff --git a/webkit/glue/plugins/pepper_plugin_module.cc b/webkit/glue/plugins/pepper_plugin_module.cc new file mode 100644 index 0000000..8ffd78b --- /dev/null +++ b/webkit/glue/plugins/pepper_plugin_module.cc @@ -0,0 +1,362 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_plugin_module.h" + +#include <set> + +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "third_party/ppapi/c/ppb_buffer.h" +#include "third_party/ppapi/c/ppb_core.h" +#include "third_party/ppapi/c/ppb_device_context_2d.h" +#include "third_party/ppapi/c/ppb_file_io.h" +#include "third_party/ppapi/c/ppb_file_io_trusted.h" +#include "third_party/ppapi/c/ppb_file_system.h" +#include "third_party/ppapi/c/ppb_image_data.h" +#include "third_party/ppapi/c/ppb_instance.h" +#include "third_party/ppapi/c/ppb_find.h" +#include "third_party/ppapi/c/ppb_font.h" +#include "third_party/ppapi/c/ppb_scrollbar.h" +#include "third_party/ppapi/c/ppb_testing.h" +#include "third_party/ppapi/c/ppb_url_loader.h" +#include "third_party/ppapi/c/ppb_url_request_info.h" +#include "third_party/ppapi/c/ppb_url_response_info.h" +#include "third_party/ppapi/c/ppb_var.h" +#include "third_party/ppapi/c/ppb_widget.h" +#include "third_party/ppapi/c/ppp.h" +#include "third_party/ppapi/c/ppp_instance.h" +#include "third_party/ppapi/c/pp_module.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "third_party/ppapi/c/pp_var.h" +#include "webkit/glue/plugins/pepper_buffer.h" +#include "webkit/glue/plugins/pepper_device_context_2d.h" +#include "webkit/glue/plugins/pepper_directory_reader.h" +#include "webkit/glue/plugins/pepper_file_io.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_file_system.h" +#include "webkit/glue/plugins/pepper_font.h" +#include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_private.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" +#include "webkit/glue/plugins/pepper_scrollbar.h" +#include "webkit/glue/plugins/pepper_url_loader.h" +#include "webkit/glue/plugins/pepper_url_request_info.h" +#include "webkit/glue/plugins/pepper_url_response_info.h" +#include "webkit/glue/plugins/pepper_var.h" +#include "webkit/glue/plugins/pepper_widget.h" +#include "webkit/glue/plugins/ppb_private.h" + +namespace pepper { + +namespace { + +// Maintains all currently loaded plugin libs for validating PP_Module +// identifiers. +typedef std::set<PluginModule*> PluginModuleSet; + +PluginModuleSet* GetLivePluginSet() { + static PluginModuleSet live_plugin_libs; + return &live_plugin_libs; +} + +base::MessageLoopProxy* GetMainThreadMessageLoop() { + static scoped_refptr<base::MessageLoopProxy> proxy( + base::MessageLoopProxy::CreateForCurrentThread()); + return proxy.get(); +} + +// PPB_Core -------------------------------------------------------------------- + +void AddRefResource(PP_Resource resource) { + if (!ResourceTracker::Get()->AddRefResource(resource)) { + DLOG(WARNING) << "AddRefResource()ing a nonexistent resource"; + } +} + +void ReleaseResource(PP_Resource resource) { + if (!ResourceTracker::Get()->UnrefResource(resource)) { + DLOG(WARNING) << "ReleaseResource()ing a nonexistent resource"; + } +} + +void* MemAlloc(size_t num_bytes) { + return malloc(num_bytes); +} + +void MemFree(void* ptr) { + free(ptr); +} + +double GetTime() { + return base::Time::Now().ToDoubleT(); +} + +void CallOnMainThread(int delay_in_msec, + PP_CompletionCallback callback, + int32_t result) { + GetMainThreadMessageLoop()->PostDelayedTask( + FROM_HERE, + NewRunnableFunction(callback.func, callback.user_data, result), + delay_in_msec); +} + +const PPB_Core core_interface = { + &AddRefResource, + &ReleaseResource, + &MemAlloc, + &MemFree, + &GetTime, + &CallOnMainThread +}; + +// PPB_Testing ----------------------------------------------------------------- + +bool ReadImageData(PP_Resource device_context_2d, + PP_Resource image, + const PP_Point* top_left) { + scoped_refptr<DeviceContext2D> context( + Resource::GetAs<DeviceContext2D>(device_context_2d)); + if (!context.get()) + return false; + return context->ReadImageData(image, top_left); +} + +void RunMessageLoop() { + bool old_state = MessageLoop::current()->NestableTasksAllowed(); + MessageLoop::current()->SetNestableTasksAllowed(true); + MessageLoop::current()->Run(); + MessageLoop::current()->SetNestableTasksAllowed(old_state); +} + +void QuitMessageLoop() { + MessageLoop::current()->Quit(); +} + +const PPB_Testing testing_interface = { + &ReadImageData, + &RunMessageLoop, + &QuitMessageLoop, +}; + +// GetInterface ---------------------------------------------------------------- + +const void* GetInterface(const char* name) { + if (strcmp(name, PPB_CORE_INTERFACE) == 0) + return &core_interface; + if (strcmp(name, PPB_VAR_INTERFACE) == 0) + return GetVarInterface(); + if (strcmp(name, PPB_INSTANCE_INTERFACE) == 0) + return PluginInstance::GetInterface(); + if (strcmp(name, PPB_IMAGEDATA_INTERFACE) == 0) + return ImageData::GetInterface(); + if (strcmp(name, PPB_DEVICECONTEXT2D_INTERFACE) == 0) + return DeviceContext2D::GetInterface(); + if (strcmp(name, PPB_URLLOADER_INTERFACE) == 0) + return URLLoader::GetInterface(); + if (strcmp(name, PPB_URLREQUESTINFO_INTERFACE) == 0) + return URLRequestInfo::GetInterface(); + if (strcmp(name, PPB_URLRESPONSEINFO_INTERFACE) == 0) + return URLResponseInfo::GetInterface(); + if (strcmp(name, PPB_BUFFER_INTERFACE) == 0) + return Buffer::GetInterface(); + if (strcmp(name, PPB_FILEREF_INTERFACE) == 0) + return FileRef::GetInterface(); + if (strcmp(name, PPB_FILEIO_INTERFACE) == 0) + return FileIO::GetInterface(); + if (strcmp(name, PPB_FILEIOTRUSTED_INTERFACE) == 0) + return FileIO::GetTrustedInterface(); + if (strcmp(name, PPB_FILESYSTEM_INTERFACE) == 0) + return FileSystem::GetInterface(); + if (strcmp(name, PPB_DIRECTORYREADER_INTERFACE) == 0) + return DirectoryReader::GetInterface(); + if (strcmp(name, PPB_WIDGET_INTERFACE) == 0) + return Widget::GetInterface(); + if (strcmp(name, PPB_SCROLLBAR_INTERFACE) == 0) + return Scrollbar::GetInterface(); + if (strcmp(name, PPB_FONT_INTERFACE) == 0) + return Font::GetInterface(); + if (strcmp(name, PPB_FIND_INTERFACE) == 0) + return PluginInstance::GetFindInterface(); + if (strcmp(name, PPB_PRIVATE_INTERFACE) == 0) + return Private::GetInterface(); + + // Only support the testing interface when the command line switch is + // specified. This allows us to prevent people from (ab)using this interface + // in production code. + if (strcmp(name, PPB_TESTING_INTERFACE) == 0) { + if (CommandLine::ForCurrentProcess()->HasSwitch("enable-pepper-testing")) + return &testing_interface; + } + return NULL; +} + +} // namespace + +PluginModule::PluginModule() + : initialized_(false), + library_(NULL) { + GetMainThreadMessageLoop(); // Initialize the main thread message loop. + GetLivePluginSet()->insert(this); +} + +PluginModule::~PluginModule() { + // When the module is being deleted, there should be no more instances still + // holding a reference to us. + DCHECK(instances_.empty()); + + GetLivePluginSet()->erase(this); + + if (entry_points_.shutdown_module) + entry_points_.shutdown_module(); + + if (library_) + base::UnloadNativeLibrary(library_); +} + +// static +scoped_refptr<PluginModule> PluginModule::CreateModule( + const FilePath& path) { + // FIXME(brettw) do uniquifying of the plugin here like the NPAPI one. + + scoped_refptr<PluginModule> lib(new PluginModule()); + if (!lib->InitFromFile(path)) + return NULL; + + return lib; +} + +scoped_refptr<PluginModule> PluginModule::CreateInternalModule( + EntryPoints entry_points) { + scoped_refptr<PluginModule> lib(new PluginModule()); + if (!lib->InitFromEntryPoints(entry_points)) + return NULL; + + return lib; +} + +// static +PluginModule* PluginModule::FromPPModule(PP_Module module) { + PluginModule* lib = reinterpret_cast<PluginModule*>(module); + if (GetLivePluginSet()->find(lib) == GetLivePluginSet()->end()) + return NULL; // Invalid plugin. + return lib; +} + +// static +const PPB_Core* PluginModule::GetCore() { + return &core_interface; +} + +bool PluginModule::InitFromEntryPoints(const EntryPoints& entry_points) { + if (initialized_) + return true; + + // Attempt to run the initialization funciton. + int retval = entry_points.initialize_module(GetPPModule(), &GetInterface); + if (retval != 0) { + LOG(WARNING) << "PPP_InitializeModule returned failure " << retval; + return false; + } + + entry_points_ = entry_points; + initialized_ = true; + return true; +} + +bool PluginModule::InitFromFile(const FilePath& path) { + if (initialized_) + return true; + + base::NativeLibrary library = base::LoadNativeLibrary(path); + if (!library) + return false; + + EntryPoints entry_points; + if (!LoadEntryPoints(library, &entry_points) || + !InitFromEntryPoints(entry_points)) { + base::UnloadNativeLibrary(library); + return false; + } + + // We let InitFromEntryPoints() handle setting the all the internal state + // of the object other than the |library_| reference. + library_ = library; + return true; +} + +// static +bool PluginModule::LoadEntryPoints(const base::NativeLibrary& library, + EntryPoints* entry_points) { + + entry_points->get_interface = + reinterpret_cast<PPP_GetInterfaceFunc>( + base::GetFunctionPointerFromNativeLibrary(library, + "PPP_GetInterface")); + if (!entry_points->get_interface) { + LOG(WARNING) << "No PPP_GetInterface in plugin library"; + return false; + } + + entry_points->initialize_module = + reinterpret_cast<PPP_InitializeModuleFunc>( + base::GetFunctionPointerFromNativeLibrary(library, + "PPP_InitializeModule")); + if (!entry_points->initialize_module) { + LOG(WARNING) << "No PPP_InitializeModule in plugin library"; + return false; + } + + // It's okay for PPP_ShutdownModule to not be defined and shutdown_module to + // be NULL. + entry_points->shutdown_module = + reinterpret_cast<PPP_ShutdownModuleFunc>( + base::GetFunctionPointerFromNativeLibrary(library, + "PPP_ShutdownModule")); + + return true; +} + +PP_Module PluginModule::GetPPModule() const { + return reinterpret_cast<intptr_t>(this); +} + +PluginInstance* PluginModule::CreateInstance(PluginDelegate* delegate) { + const PPP_Instance* plugin_instance_interface = + reinterpret_cast<const PPP_Instance*>(GetPluginInterface( + PPP_INSTANCE_INTERFACE)); + if (!plugin_instance_interface) { + LOG(WARNING) << "Plugin doesn't support instance interface, failing."; + return NULL; + } + return new PluginInstance(delegate, this, plugin_instance_interface); +} + +PluginInstance* PluginModule::GetSomeInstance() const { + // This will generally crash later if there is not actually any instance to + // return, so we force a crash now to make bugs easier to track down. + CHECK(!instances_.empty()); + return *instances_.begin(); +} + +const void* PluginModule::GetPluginInterface(const char* name) const { + if (!entry_points_.get_interface) + return NULL; + return entry_points_.get_interface(name); +} + +void PluginModule::InstanceCreated(PluginInstance* instance) { + instances_.insert(instance); +} + +void PluginModule::InstanceDeleted(PluginInstance* instance) { + instances_.erase(instance); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_plugin_module.h b/webkit/glue/plugins/pepper_plugin_module.h new file mode 100644 index 0000000..6bfeccf --- /dev/null +++ b/webkit/glue/plugins/pepper_plugin_module.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/native_library.h" +#include "base/ref_counted.h" +#include "third_party/ppapi/c/pp_module.h" +#include "third_party/ppapi/c/ppb.h" + +typedef struct _ppb_Core PPB_Core; + +namespace pepper { + +class PluginDelegate; +class PluginInstance; + +class PluginModule : public base::RefCounted<PluginModule> { + public: + typedef const void* (*PPP_GetInterfaceFunc)(const char*); + typedef int (*PPP_InitializeModuleFunc)(PP_Module, PPB_GetInterface); + typedef void (*PPP_ShutdownModuleFunc)(); + + struct EntryPoints { + EntryPoints() + : get_interface(NULL), + initialize_module(NULL), + shutdown_module(NULL) { + } + + PPP_GetInterfaceFunc get_interface; + PPP_InitializeModuleFunc initialize_module; + PPP_ShutdownModuleFunc shutdown_module; + }; + + ~PluginModule(); + + static scoped_refptr<PluginModule> CreateModule(const FilePath& path); + static scoped_refptr<PluginModule> CreateInternalModule( + EntryPoints entry_points); + + // Converts the given module ID to an actual module object. Will return NULL + // if the module is invalid. + static PluginModule* FromPPModule(PP_Module module); + + static const PPB_Core* GetCore(); + + PP_Module GetPPModule() const; + + PluginInstance* CreateInstance(PluginDelegate* delegate); + + // Returns "some" plugin instance associated with this module. This is not + // guaranteed to be any one in particular. This is normally used to execute + // callbacks up to the browser layer that are not inherently per-instance, + // but the delegate lives only on the plugin instance so we need one of them. + PluginInstance* GetSomeInstance() const; + + const void* GetPluginInterface(const char* name) const; + + // This module is associated with a set of instances. The PluginInstance + // object declares its association with this module in its destructor and + // releases us in its destructor. + void InstanceCreated(PluginInstance* instance); + void InstanceDeleted(PluginInstance* instance); + + private: + PluginModule(); + + bool InitFromEntryPoints(const EntryPoints& entry_points); + bool InitFromFile(const FilePath& path); + static bool LoadEntryPoints(const base::NativeLibrary& library, + EntryPoints* entry_points); + + bool initialized_; + + // Holds a reference to the base::NativeLibrary handle if this PluginModule + // instance wraps functions loaded from a library. Can be NULL. If + // |library_| is non-NULL, PluginModule will attempt to unload the library + // during destruction. + base::NativeLibrary library_; + + // Contains pointers to the entry points of the actual plugin + // implementation. + EntryPoints entry_points_; + + // Non-owning pointers to all instances associated with this module. When + // there are no more instances, this object should be deleted. + typedef std::set<PluginInstance*> PluginInstanceSet; + PluginInstanceSet instances_; + + DISALLOW_COPY_AND_ASSIGN(PluginModule); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PLUGIN_MODULE_H_ diff --git a/webkit/glue/plugins/pepper_private.cc b/webkit/glue/plugins/pepper_private.cc new file mode 100644 index 0000000..8d5182f --- /dev/null +++ b/webkit/glue/plugins/pepper_private.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2010 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 "build/build_config.h" + +#include "webkit/glue/plugins/pepper_private.h" + +#include "base/utf_string_conversions.h" +#include "grit/webkit_strings.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/pepper_var.h" +#include "webkit/glue/plugins/ppb_private.h" + +namespace pepper { + +namespace { + +PP_Var GetLocalizedString(PP_ResourceString string_id) { + std::string rv; + if (string_id == PP_RESOURCESTRING_PDFGETPASSWORD) + rv = UTF16ToUTF8(webkit_glue::GetLocalizedString(IDS_PDF_NEED_PASSWORD)); + + return StringToPPVar(rv); +} + +const PPB_Private ppb_private = { + &GetLocalizedString, +}; + +} // namespace + +// static +const PPB_Private* Private::GetInterface() { + return &ppb_private; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_private.h b/webkit/glue/plugins/pepper_private.h new file mode 100644 index 0000000..fda75a7 --- /dev/null +++ b/webkit/glue/plugins/pepper_private.h @@ -0,0 +1,23 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_PRIVATE_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_PRIVATE_H_ + +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _ppb_Private PPB_Private; + +namespace pepper { + +class Private { + public: + // Returns a pointer to the interface implementing PPB_Private that is exposed + // to the plugin. + static const PPB_Private* GetInterface(); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_PRIVATE_H_ diff --git a/webkit/glue/plugins/pepper_resource.cc b/webkit/glue/plugins/pepper_resource.cc new file mode 100644 index 0000000..c568183 --- /dev/null +++ b/webkit/glue/plugins/pepper_resource.cc @@ -0,0 +1,19 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_resource.h" + +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +PP_Resource Resource::GetReference() { + ResourceTracker *tracker = ResourceTracker::Get(); + if (resource_id_) + tracker->AddRefResource(resource_id_); + else + resource_id_ = tracker->AddResource(this); + return resource_id_; +} +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_resource.h b/webkit/glue/plugins/pepper_resource.h new file mode 100644 index 0000000..417a06b --- /dev/null +++ b/webkit/glue/plugins/pepper_resource.h @@ -0,0 +1,135 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_ + +#include "base/logging.h" +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "webkit/glue/plugins/pepper_resource_tracker.h" + +namespace pepper { + +class Buffer; +class DeviceContext2D; +class DirectoryReader; +class FileChooser; +class FileIO; +class FileRef; +class Font; +class ImageData; +class PluginModule; +class Scrollbar; +class URLLoader; +class URLRequestInfo; +class URLResponseInfo; +class Widget; + +class Resource : public base::RefCountedThreadSafe<Resource> { + public: + explicit Resource(PluginModule* module) : resource_id_(0), module_(module) {} + virtual ~Resource() {} + + // Returns NULL if the resource is invalid or is a different type. + template<typename T> + static scoped_refptr<T> GetAs(PP_Resource res) { + scoped_refptr<Resource> resource = ResourceTracker::Get()->GetResource(res); + return resource ? resource->Cast<T>() : NULL; + } + + PluginModule* module() const { return module_; } + + // Cast the resource into a specified type. This will return NULL if the + // resource does not match the specified type. Specializations of this + // template call into As* functions. + template <typename T> T* Cast() { return NULL; } + + // Returns an resource id of this object. If the object doesn't have a + // resource id, new one is created with plugin refcount of 1. If it does, + // the refcount is incremented. Use this when you need to return a new + // reference to the plugin. + PP_Resource GetReference(); + + // When you need to ensure that a resource has a reference, but you do not + // want to increase the refcount (for example, if you need to call a plugin + // callback function with a reference), you can use this class. For example: + // + // plugin_callback(.., ScopedResourceId(resource).id, ...); + class ScopedResourceId { + public: + explicit ScopedResourceId(Resource* resource) + : id(resource->GetReference()) {} + ~ScopedResourceId() { + ResourceTracker::Get()->UnrefResource(id); + } + const PP_Resource id; + }; + + private: + // Type-specific getters for individual resource types. These will return + // NULL if the resource does not match the specified type. Used by the Cast() + // function. + virtual Buffer* AsBuffer() { return NULL; } + virtual DeviceContext2D* AsDeviceContext2D() { return NULL; } + virtual DirectoryReader* AsDirectoryReader() { return NULL; } + virtual FileChooser* AsFileChooser() { return NULL; } + virtual FileIO* AsFileIO() { return NULL; } + virtual FileRef* AsFileRef() { return NULL; } + virtual Font* AsFont() { return NULL; } + virtual ImageData* AsImageData() { return NULL; } + virtual Scrollbar* AsScrollbar() { return NULL; } + virtual URLLoader* AsURLLoader() { return NULL; } + virtual URLRequestInfo* AsURLRequestInfo() { return NULL; } + virtual URLResponseInfo* AsURLResponseInfo() { return NULL; } + virtual Widget* AsWidget() { return NULL; } + + private: + // If referenced by a plugin, holds the id of this resource object. Do not + // access this member directly, because it is possible that the plugin holds + // no references to the object, and therefore the resource_id_ is zero. Use + // either GetReference() to obtain a new resource_id and increase the + // refcount, or TemporaryReference when you do not want to increase the + // refcount. + PP_Resource resource_id_; + + // Non-owning pointer to our module. + PluginModule* module_; + + // Called by the resource tracker when the last plugin reference has been + // dropped. + friend class ResourceTracker; + void StoppedTracking() { + DCHECK(resource_id_ != 0); + resource_id_ = 0; + } + + DISALLOW_COPY_AND_ASSIGN(Resource); +}; + +// Cast() specializations. +#define DEFINE_RESOURCE_CAST(Type) \ + template <> inline Type* Resource::Cast<Type>() { \ + return As##Type(); \ + } + +DEFINE_RESOURCE_CAST(Buffer) +DEFINE_RESOURCE_CAST(DeviceContext2D) +DEFINE_RESOURCE_CAST(DirectoryReader) +DEFINE_RESOURCE_CAST(FileChooser) +DEFINE_RESOURCE_CAST(FileIO) +DEFINE_RESOURCE_CAST(FileRef) +DEFINE_RESOURCE_CAST(Font) +DEFINE_RESOURCE_CAST(ImageData) +DEFINE_RESOURCE_CAST(Scrollbar) +DEFINE_RESOURCE_CAST(URLLoader) +DEFINE_RESOURCE_CAST(URLRequestInfo) +DEFINE_RESOURCE_CAST(URLResponseInfo) +DEFINE_RESOURCE_CAST(Widget) + +#undef DEFINE_RESOURCE_CAST +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_H_ diff --git a/webkit/glue/plugins/pepper_resource_tracker.cc b/webkit/glue/plugins/pepper_resource_tracker.cc new file mode 100644 index 0000000..8aa94d2 --- /dev/null +++ b/webkit/glue/plugins/pepper_resource_tracker.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_resource_tracker.h" + +#include <limits> +#include <set> + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_resource.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace pepper { + +scoped_refptr<Resource> ResourceTracker::GetResource(PP_Resource res) const { + ResourceMap::const_iterator result = live_resources_.find(res); + if (result == live_resources_.end()) { + return scoped_refptr<Resource>(); + } + return result->second.first; +} + +PP_Resource ResourceTracker::AddResource(Resource* resource) { + // If the plugin manages to create 4B resources... + if (last_id_ == std::numeric_limits<PP_Resource>::max()) { + return 0; + } + // Add the resource with plugin use-count 1. + ++last_id_; + live_resources_.insert(std::make_pair(last_id_, std::make_pair(resource, 1))); + return last_id_; +} + +bool ResourceTracker::AddRefResource(PP_Resource res) { + ResourceMap::iterator i = live_resources_.find(res); + if (i != live_resources_.end()) { + // We don't protect against overflow, since a plugin as malicious as to ref + // once per every byte in the address space could have just as well unrefed + // one time too many. + ++i->second.second; + return true; + } else { + return false; + } +} + +bool ResourceTracker::UnrefResource(PP_Resource res) { + ResourceMap::iterator i = live_resources_.find(res); + if (i != live_resources_.end()) { + if (!--i->second.second) { + i->second.first->StoppedTracking(); + live_resources_.erase(i); + } + return true; + } else { + return false; + } +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_resource_tracker.h b/webkit/glue/plugins/pepper_resource_tracker.h new file mode 100644 index 0000000..d06c9ba --- /dev/null +++ b/webkit/glue/plugins/pepper_resource_tracker.h @@ -0,0 +1,75 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_ + +#include <set> + +#include "base/atomic_sequence_num.h" +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/ref_counted.h" +#include "base/singleton.h" +#include "third_party/ppapi/c/pp_resource.h" + +namespace pepper { + +class Resource; + +// This class maintains a global list of all live pepper resources. It allows +// us to check resource ID validity and to map them to a specific module. +// +// This object is threadsafe. +class ResourceTracker { + public: + // Returns the pointer to the singleton object. + static ResourceTracker* Get() { + return Singleton<ResourceTracker>::get(); + } + + // The returned pointer will be NULL if there is no resource. Note that this + // return value is a scoped_refptr so that we ensure the resource is valid + // from the point of the lookup to the point that the calling code needs it. + // Otherwise, the plugin could Release() the resource on another thread and + // the object will get deleted out from under us. + scoped_refptr<Resource> GetResource(PP_Resource res) const; + + // Increment resource's plugin refcount. See ResourceAndRefCount comments + // below. + bool AddRefResource(PP_Resource res); + bool UnrefResource(PP_Resource res); + + private: + friend struct DefaultSingletonTraits<ResourceTracker>; + friend class Resource; + + // Prohibit creation other then by the Singleton class. + ResourceTracker() : last_id_(0) {} + ~ResourceTracker() {} + + // Adds the given resource to the tracker and assigns it a resource ID and + // refcount of 1. The assigned resource ID will be returned. Used only by the + // Resource class. + PP_Resource AddResource(Resource* resource); + + // Last assigned resource ID. + PP_Resource last_id_; + + // For each PP_Resource, keep the Resource* (as refptr) and plugin use count. + // This use count is different then Resource's RefCount, and is manipulated + // using this RefResource/UnrefResource. When it drops to zero, we just remove + // the resource from this resource tracker, but the resource object will be + // alive so long as some scoped_refptr still holds it's reference. This + // prevents plugins from forcing destruction of Resource objects. + typedef std::pair<scoped_refptr<Resource>, size_t> ResourceAndRefCount; + typedef base::hash_map<PP_Resource, ResourceAndRefCount> ResourceMap; + ResourceMap live_resources_; + + DISALLOW_COPY_AND_ASSIGN(ResourceTracker); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_RESOURCE_TRACKER_H_ diff --git a/webkit/glue/plugins/pepper_scrollbar.cc b/webkit/glue/plugins/pepper_scrollbar.cc new file mode 100644 index 0000000..48db8d4 --- /dev/null +++ b/webkit/glue/plugins/pepper_scrollbar.cc @@ -0,0 +1,228 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_scrollbar.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "third_party/ppapi/c/ppp_scrollbar.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScrollbar.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/plugins/pepper_event_conversion.h" +#include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebInputEvent; +using WebKit::WebRect; +using WebKit::WebScrollbar; + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Instance instance_id, bool vertical) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return NULL; + + scoped_refptr<Scrollbar> scrollbar(new Scrollbar(instance, vertical)); + return scrollbar->GetReference(); +} + +bool IsScrollbar(PP_Resource resource) { + return !!Resource::GetAs<Scrollbar>(resource); +} + +uint32_t GetThickness() { + return WebScrollbar::defaultThickness(); +} + +uint32_t GetValue(PP_Resource resource) { + scoped_refptr<Scrollbar> scrollbar(Resource::GetAs<Scrollbar>(resource)); + if (!scrollbar) + return 0; + return scrollbar->GetValue(); +} + +void SetValue(PP_Resource resource, uint32_t value) { + scoped_refptr<Scrollbar> scrollbar(Resource::GetAs<Scrollbar>(resource)); + if (scrollbar) + scrollbar->SetValue(value); +} + +void SetDocumentSize(PP_Resource resource, uint32_t size) { + scoped_refptr<Scrollbar> scrollbar(Resource::GetAs<Scrollbar>(resource)); + if (scrollbar) + scrollbar->SetDocumentSize(size); +} + +void SetTickMarks(PP_Resource resource, + const PP_Rect* tick_marks, + uint32_t count) { + scoped_refptr<Scrollbar> scrollbar(Resource::GetAs<Scrollbar>(resource)); + if (scrollbar) + scrollbar->SetTickMarks(tick_marks, count); +} + +void ScrollBy(PP_Resource resource, + PP_ScrollBy unit, + int32_t multiplier) { + scoped_refptr<Scrollbar> scrollbar(Resource::GetAs<Scrollbar>(resource)); + if (scrollbar) + scrollbar->ScrollBy(unit, multiplier); +} + +const PPB_Scrollbar ppb_scrollbar = { + &Create, + &IsScrollbar, + &GetThickness, + &GetValue, + &SetValue, + &SetDocumentSize, + &SetTickMarks, + &ScrollBy +}; + +} // namespace + +Scrollbar::Scrollbar(PluginInstance* instance, bool vertical) + : Widget(instance) { + scrollbar_.reset(WebScrollbar::create( + static_cast<WebKit::WebScrollbarClient*>(this), + vertical ? WebScrollbar::Vertical : WebScrollbar::Horizontal)); +} + +Scrollbar::~Scrollbar() { +} + +// static +const PPB_Scrollbar* Scrollbar::GetInterface() { + return &ppb_scrollbar; +} + +uint32_t Scrollbar::GetValue() { + return scrollbar_->value(); +} + +void Scrollbar::SetValue(uint32_t value) { + scrollbar_->setValue(value); +} + +void Scrollbar::SetDocumentSize(uint32_t size) { + scrollbar_->setDocumentSize(size); +} + +void Scrollbar::SetTickMarks(const PP_Rect* tick_marks, uint32_t count) { + tickmarks_.resize(count); + for (uint32 i = 0; i < count; ++i) { + tickmarks_[i] = WebRect(tick_marks[i].point.x, + tick_marks[i].point.y, + tick_marks[i].size.width, + tick_marks[i].size.height);; + } + PP_Rect rect = location(); + Invalidate(&rect); +} + +void Scrollbar::ScrollBy(PP_ScrollBy unit, int32_t multiplier) { + WebScrollbar::ScrollDirection direction = multiplier >= 0 ? + WebScrollbar::ScrollForward : WebScrollbar::ScrollBackward; + float fmultiplier = 1.0; + + WebScrollbar::ScrollGranularity granularity; + if (unit == PP_SCROLLBY_LINE) { + granularity = WebScrollbar::ScrollByLine; + } else if (unit == PP_SCROLLBY_PAGE) { + granularity = WebScrollbar::ScrollByPage; + } else if (unit == PP_SCROLLBY_DOCUMENT) { + granularity = WebScrollbar::ScrollByDocument; + } else { + granularity = WebScrollbar::ScrollByPixel; + fmultiplier = static_cast<float>(multiplier); + if (fmultiplier < 0) + fmultiplier *= -1; + } + scrollbar_->scroll(direction, granularity, fmultiplier); +} + +bool Scrollbar::Paint(const PP_Rect* rect, ImageData* image) { + gfx::Rect gfx_rect(rect->point.x, + rect->point.y, + rect->size.width, + rect->size.height); + skia::PlatformCanvas* canvas = image->mapped_canvas(); + if (!canvas) + return false; + scrollbar_->paint(webkit_glue::ToWebCanvas(canvas), gfx_rect); + return true; +} + +bool Scrollbar::HandleEvent(const PP_Event* event) { + scoped_ptr<WebInputEvent> web_input_event(CreateWebInputEvent(*event)); + if (!web_input_event.get()) + return false; + + return scrollbar_->handleInputEvent(*web_input_event.get()); +} + +void Scrollbar::SetLocationInternal(const PP_Rect* location) { + scrollbar_->setLocation(WebRect(location->point.x, + location->point.y, + location->size.width, + location->size.height)); +} + +void Scrollbar::valueChanged(WebKit::WebScrollbar* scrollbar) { + const PPP_Scrollbar* ppp_scrollbar = static_cast<const PPP_Scrollbar*>( + module()->GetPluginInterface(PPP_SCROLLBAR_INTERFACE)); + if (!ppp_scrollbar) + return; + ScopedResourceId resource(this); + ppp_scrollbar->ValueChanged( + instance()->GetPPInstance(), resource.id, scrollbar_->value()); +} + +void Scrollbar::invalidateScrollbarRect(WebKit::WebScrollbar* scrollbar, + const WebKit::WebRect& rect) { + gfx::Rect gfx_rect(rect.x, + rect.y, + rect.width, + rect.height); + dirty_ = dirty_.Union(gfx_rect); + // Can't call into the client to tell them about the invalidate right away, + // since the Scrollbar code is still in the middle of updating its internal + // state. + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &Scrollbar::NotifyInvalidate)); +} + +void Scrollbar::getTickmarks( + WebKit::WebScrollbar* scrollbar, + WebKit::WebVector<WebKit::WebRect>* tick_marks) const { + if (tickmarks_.empty()) { + WebRect* rects = NULL; + tick_marks->assign(rects, 0); + } else { + tick_marks->assign(&tickmarks_[0], tickmarks_.size()); + } +} + +void Scrollbar::NotifyInvalidate() { + if (dirty_.IsEmpty()) + return; + PP_Rect pp_rect; + pp_rect.point.x = dirty_.x(); + pp_rect.point.y = dirty_.y(); + pp_rect.size.width = dirty_.width(); + pp_rect.size.height = dirty_.height(); + dirty_ = gfx::Rect(); + Invalidate(&pp_rect); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_scrollbar.h b/webkit/glue/plugins/pepper_scrollbar.h new file mode 100644 index 0000000..bf25136 --- /dev/null +++ b/webkit/glue/plugins/pepper_scrollbar.h @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_SCROLLBAR_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_SCROLLBAR_H_ + +#include <vector> + +#include "gfx/rect.h" +#include "third_party/ppapi/c/ppb_scrollbar.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScrollbarClient.h" +#include "webkit/glue/plugins/pepper_widget.h" + +typedef struct _ppb_Scrollbar PPB_Scrollbar; + +namespace pepper { + +class PluginInstance; + +class Scrollbar : public Widget, public WebKit::WebScrollbarClient { + public: + Scrollbar(PluginInstance* instance, bool vertical); + virtual ~Scrollbar(); + + // Returns a pointer to the interface implementing PPB_Scrollbar that is + // exposed to the plugin. + static const PPB_Scrollbar* GetInterface(); + + // Resource overrides. + Scrollbar* AsScrollbar() { return this; } + + // PPB_Scrollbar implementation. + uint32_t GetValue(); + void SetValue(uint32_t value); + void SetDocumentSize(uint32_t size); + void SetTickMarks(const PP_Rect* tick_marks, uint32_t count); + void ScrollBy(PP_ScrollBy unit, int32_t multiplier); + + // PPB_Widget implementation. + virtual bool Paint(const PP_Rect* rect, ImageData* image); + virtual bool HandleEvent(const PP_Event* event); + virtual void SetLocationInternal(const PP_Rect* location); + + private: + // WebKit::WebScrollbarClient implementation. + virtual void valueChanged(WebKit::WebScrollbar* scrollbar); + virtual void invalidateScrollbarRect(WebKit::WebScrollbar* scrollbar, + const WebKit::WebRect& rect); + virtual void getTickmarks( + WebKit::WebScrollbar* scrollbar, + WebKit::WebVector<WebKit::WebRect>* tick_marks) const; + + void NotifyInvalidate(); + + gfx::Rect dirty_; + std::vector<WebKit::WebRect> tickmarks_; + scoped_ptr<WebKit::WebScrollbar> scrollbar_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_SCROLLBAR_H_ diff --git a/webkit/glue/plugins/pepper_string.h b/webkit/glue/plugins/pepper_string.h new file mode 100644 index 0000000..1fc43c4 --- /dev/null +++ b/webkit/glue/plugins/pepper_string.h @@ -0,0 +1,30 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +namespace pepper { + +class String : public base::RefCountedThreadSafe<String> { + public: + String(const char* str, uint32 len) : value_(str, len) { + } + + const std::string& value() const { return value_; } + + private: + std::string value_; + + DISALLOW_COPY_AND_ASSIGN(String); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_STRING_H_ diff --git a/webkit/glue/plugins/pepper_url_loader.cc b/webkit/glue/plugins/pepper_url_loader.cc new file mode 100644 index 0000000..aa09686 --- /dev/null +++ b/webkit/glue/plugins/pepper_url_loader.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_url_loader.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "third_party/ppapi/c/ppb_url_loader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_url_request_info.h" +#include "webkit/glue/plugins/pepper_url_response_info.h" + +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + +#ifdef _MSC_VER +// Do not warn about use of std::copy with raw pointers. +#pragma warning(disable : 4996) +#endif + +namespace pepper { + +namespace { + +PP_Resource Create(PP_Instance instance_id) { + PluginInstance* instance = PluginInstance::FromPPInstance(instance_id); + if (!instance) + return 0; + + URLLoader* loader = new URLLoader(instance); + + return loader->GetReference(); +} + +bool IsURLLoader(PP_Resource resource) { + return !!Resource::GetAs<URLLoader>(resource); +} + +int32_t Open(PP_Resource loader_id, + PP_Resource request_id, + PP_CompletionCallback callback) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return PP_ERROR_BADRESOURCE; + + scoped_refptr<URLRequestInfo> request( + Resource::GetAs<URLRequestInfo>(request_id)); + if (!request) + return PP_ERROR_BADRESOURCE; + + return loader->Open(request, callback); +} + +int32_t FollowRedirect(PP_Resource loader_id, + PP_CompletionCallback callback) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return PP_ERROR_BADRESOURCE; + + return loader->FollowRedirect(callback); +} + +bool GetUploadProgress(PP_Resource loader_id, + int64_t* bytes_sent, + int64_t* total_bytes_to_be_sent) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return false; + + *bytes_sent = loader->bytes_sent(); + *total_bytes_to_be_sent = loader->total_bytes_to_be_sent(); + return true; +} + +bool GetDownloadProgress(PP_Resource loader_id, + int64_t* bytes_received, + int64_t* total_bytes_to_be_received) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return false; + + *bytes_received = loader->bytes_received(); + *total_bytes_to_be_received = loader->total_bytes_to_be_received(); + return true; +} + +PP_Resource GetResponseInfo(PP_Resource loader_id) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return 0; + + URLResponseInfo* response_info = loader->response_info(); + if (!response_info) + return 0; + + return response_info->GetReference(); +} + +int32_t ReadResponseBody(PP_Resource loader_id, + char* buffer, + int32_t bytes_to_read, + PP_CompletionCallback callback) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return PP_ERROR_BADRESOURCE; + + return loader->ReadResponseBody(buffer, bytes_to_read, callback); +} + +void Close(PP_Resource loader_id) { + scoped_refptr<URLLoader> loader(Resource::GetAs<URLLoader>(loader_id)); + if (!loader) + return; + + loader->Close(); +} + +const PPB_URLLoader ppb_urlloader = { + &Create, + &IsURLLoader, + &Open, + &FollowRedirect, + &GetUploadProgress, + &GetDownloadProgress, + &GetResponseInfo, + &ReadResponseBody, + &Close +}; + +} // namespace + +URLLoader::URLLoader(PluginInstance* instance) + : Resource(instance->module()), + instance_(instance), + pending_callback_(), + bytes_sent_(0), + total_bytes_to_be_sent_(0), + bytes_received_(0), + total_bytes_to_be_received_(0), + user_buffer_(NULL), + user_buffer_size_(0), + done_(false) { +} + +URLLoader::~URLLoader() { +} + +// static +const PPB_URLLoader* URLLoader::GetInterface() { + return &ppb_urlloader; +} + +int32_t URLLoader::Open(URLRequestInfo* request, + PP_CompletionCallback callback) { + if (loader_.get()) + return PP_ERROR_INPROGRESS; + + // We only support non-blocking calls. + if (!callback.func) + return PP_ERROR_BADARGUMENT; + + WebFrame* frame = instance_->container()->element().document().frame(); + if (!frame) + return PP_ERROR_FAILED; + WebURLRequest web_request(request->ToWebURLRequest(frame)); + frame->dispatchWillSendRequest(web_request); + + loader_.reset(WebKit::webKitClient()->createURLLoader()); + if (!loader_.get()) { + loader_.reset(); + return PP_ERROR_FAILED; + } + loader_->loadAsynchronously(web_request, this); + + pending_callback_ = callback; + + // Notify completion when we receive a redirect or response headers. + return PP_ERROR_WOULDBLOCK; +} + +int32_t URLLoader::FollowRedirect(PP_CompletionCallback callback) { + NOTIMPLEMENTED(); // TODO(darin): Implement me. + return PP_ERROR_FAILED; +} + +int32_t URLLoader::ReadResponseBody(char* buffer, int32_t bytes_to_read, + PP_CompletionCallback callback) { + if (bytes_to_read <= 0 || !buffer) + return PP_ERROR_BADARGUMENT; + if (pending_callback_.func) + return PP_ERROR_INPROGRESS; + + // We only support non-blocking calls. + if (!callback.func) + return PP_ERROR_BADARGUMENT; + + user_buffer_ = buffer; + user_buffer_size_ = bytes_to_read; + + if (!buffer_.empty()) + return FillUserBuffer(); + + if (done_) { + user_buffer_ = NULL; + user_buffer_size_ = 0; + return 0; + } + + pending_callback_ = callback; + return PP_ERROR_WOULDBLOCK; +} + +void URLLoader::Close() { + NOTIMPLEMENTED(); // TODO(darin): Implement me. +} + +void URLLoader::willSendRequest(WebURLLoader* loader, + WebURLRequest& new_request, + const WebURLResponse& redirect_response) { + NOTIMPLEMENTED(); // TODO(darin): Allow the plugin to inspect redirects. +} + +void URLLoader::didSendData(WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + // TODO(darin): Bounds check input? + bytes_sent_ = static_cast<int64_t>(bytes_sent); + total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent); +} + +void URLLoader::didReceiveResponse(WebURLLoader* loader, + const WebURLResponse& response) { + scoped_refptr<URLResponseInfo> response_info(new URLResponseInfo(module())); + if (response_info->Initialize(response)) + response_info_ = response_info; + + RunCallback(PP_OK); +} + +void URLLoader::didReceiveData(WebURLLoader* loader, + const char* data, + int data_length) { + buffer_.insert(buffer_.end(), data, data + data_length); + if (user_buffer_) { + RunCallback(FillUserBuffer()); + } else { + DCHECK(!pending_callback_.func); + } +} + +void URLLoader::didFinishLoading(WebURLLoader* loader) { + done_ = true; + RunCallback(PP_OK); +} + +void URLLoader::didFail(WebURLLoader* loader, const WebURLError& error) { + done_ = true; + // TODO(darin): Provide more detailed error information. + RunCallback(PP_ERROR_FAILED); +} + +void URLLoader::RunCallback(int32_t result) { + if (!pending_callback_.func) + return; + + PP_CompletionCallback callback = {0}; + std::swap(callback, pending_callback_); + PP_RunCompletionCallback(&callback, result); +} + +size_t URLLoader::FillUserBuffer() { + DCHECK(user_buffer_); + DCHECK(user_buffer_size_); + + size_t bytes_to_copy = std::min(buffer_.size(), user_buffer_size_); + std::copy(buffer_.begin(), buffer_.begin() + bytes_to_copy, user_buffer_); + buffer_.erase(buffer_.begin(), buffer_.begin() + bytes_to_copy); + + // Reset for next time. + user_buffer_ = NULL; + user_buffer_size_ = 0; + return bytes_to_copy; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_url_loader.h b/webkit/glue/plugins/pepper_url_loader.h new file mode 100644 index 0000000..088f220 --- /dev/null +++ b/webkit/glue/plugins/pepper_url_loader.h @@ -0,0 +1,89 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_URL_LOADER_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_URL_LOADER_H_ + +#include <deque> + +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _ppb_URLLoader PPB_URLLoader; + +namespace pepper { + +class PluginInstance; +class URLRequestInfo; +class URLResponseInfo; + +class URLLoader : public Resource, public WebKit::WebURLLoaderClient { + public: + explicit URLLoader(PluginInstance* instance); + virtual ~URLLoader(); + + // Returns a pointer to the interface implementing PPB_URLLoader that is + // exposed to the plugin. + static const PPB_URLLoader* GetInterface(); + + // Resource overrides. + URLLoader* AsURLLoader() { return this; } + + // PPB_URLLoader implementation. + int32_t Open(URLRequestInfo* request, PP_CompletionCallback callback); + int32_t FollowRedirect(PP_CompletionCallback callback); + int32_t ReadResponseBody(char* buffer, int32_t bytes_to_read, + PP_CompletionCallback callback); + void Close(); + + // WebKit::WebURLLoaderClient implementation. + virtual void willSendRequest(WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& new_request, + const WebKit::WebURLResponse& redir_response); + virtual void didSendData(WebKit::WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent); + virtual void didReceiveResponse(WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didReceiveData(WebKit::WebURLLoader* loader, + const char* data, + int data_length); + virtual void didFinishLoading(WebKit::WebURLLoader* loader); + virtual void didFail(WebKit::WebURLLoader* loader, + const WebKit::WebURLError& error); + + URLResponseInfo* response_info() const { return response_info_; } + + // Progress counters. + int64_t bytes_sent() const { return bytes_sent_; } + int64_t total_bytes_to_be_sent() const { return total_bytes_to_be_sent_; } + int64_t bytes_received() const { return bytes_received_; } + int64_t total_bytes_to_be_received() const { + return total_bytes_to_be_received_; + } + + private: + void RunCallback(int32_t result); + size_t FillUserBuffer(); + + scoped_refptr<PluginInstance> instance_; + scoped_ptr<WebKit::WebURLLoader> loader_; + scoped_refptr<URLResponseInfo> response_info_; + PP_CompletionCallback pending_callback_; + std::deque<char> buffer_; + int64_t bytes_sent_; + int64_t total_bytes_to_be_sent_; + int64_t bytes_received_; + int64_t total_bytes_to_be_received_; + char* user_buffer_; + size_t user_buffer_size_; + bool done_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_URL_LOADER_H_ diff --git a/webkit/glue/plugins/pepper_url_request_info.cc b/webkit/glue/plugins/pepper_url_request_info.cc new file mode 100644 index 0000000..d230f20 --- /dev/null +++ b/webkit/glue/plugins/pepper_url_request_info.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_url_request_info.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/http/http_util.h" +#include "third_party/ppapi/c/pp_var.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/plugins/pepper_string.h" +#include "webkit/glue/plugins/pepper_var.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebData; +using WebKit::WebFileInfo; +using WebKit::WebHTTPBody; +using WebKit::WebString; +using WebKit::WebFrame; +using WebKit::WebURL; +using WebKit::WebURLRequest; + +namespace pepper { + +namespace { + +// If any of these request headers are specified, they will not be sent. +// TODO(darin): Add more based on security considerations? +const char* const kIgnoredRequestHeaders[] = { + "content-length" +}; + +bool IsIgnoredRequestHeader(const std::string& name) { + for (size_t i = 0; i < arraysize(kIgnoredRequestHeaders); ++i) { + if (LowerCaseEqualsASCII(name, kIgnoredRequestHeaders[i])) + return true; + } + return false; +} + +PP_Resource Create(PP_Module module_id) { + PluginModule* module = PluginModule::FromPPModule(module_id); + if (!module) + return 0; + + URLRequestInfo* request = new URLRequestInfo(module); + + return request->GetReference(); +} + +bool IsURLRequestInfo(PP_Resource resource) { + return !!Resource::GetAs<URLRequestInfo>(resource); +} + +bool SetProperty(PP_Resource request_id, + PP_URLRequestProperty property, + PP_Var var) { + scoped_refptr<URLRequestInfo> request( + Resource::GetAs<URLRequestInfo>(request_id)); + if (!request) + return false; + + if (var.type == PP_VARTYPE_BOOL) + return request->SetBooleanProperty(property, var.value.as_bool); + + if (var.type == PP_VARTYPE_STRING) + return request->SetStringProperty(property, GetString(var)->value()); + + return false; +} + +bool AppendDataToBody(PP_Resource request_id, PP_Var var) { + scoped_refptr<URLRequestInfo> request( + Resource::GetAs<URLRequestInfo>(request_id)); + if (!request) + return false; + + String* data = GetString(var); + if (!data) + return false; + + return request->AppendDataToBody(data->value()); +} + +bool AppendFileToBody(PP_Resource request_id, + PP_Resource file_ref_id, + int64_t start_offset, + int64_t number_of_bytes, + PP_Time expected_last_modified_time) { + scoped_refptr<URLRequestInfo> request( + Resource::GetAs<URLRequestInfo>(request_id)); + if (!request) + return false; + + scoped_refptr<FileRef> file_ref(Resource::GetAs<FileRef>(file_ref_id)); + if (!file_ref) + return false; + + return request->AppendFileToBody(file_ref, + start_offset, + number_of_bytes, + expected_last_modified_time); +} + +const PPB_URLRequestInfo ppb_urlrequestinfo = { + &Create, + &IsURLRequestInfo, + &SetProperty, + &AppendDataToBody, + &AppendFileToBody +}; + +} // namespace + +URLRequestInfo::URLRequestInfo(PluginModule* module) + : Resource(module) { +} + +URLRequestInfo::~URLRequestInfo() { +} + +// static +const PPB_URLRequestInfo* URLRequestInfo::GetInterface() { + return &ppb_urlrequestinfo; +} + +bool URLRequestInfo::SetBooleanProperty(PP_URLRequestProperty property, + bool value) { + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return false; +} + +bool URLRequestInfo::SetStringProperty(PP_URLRequestProperty property, + const std::string& value) { + // TODO(darin): Validate input. Perhaps at a different layer? + switch (property) { + case PP_URLREQUESTPROPERTY_URL: + url_ = value; // NOTE: This may be a relative URL. + return true; + case PP_URLREQUESTPROPERTY_METHOD: + method_ = value; + return true; + case PP_URLREQUESTPROPERTY_HEADERS: + headers_ = value; + return true; + default: + return false; + } +} + +bool URLRequestInfo::AppendDataToBody(const std::string& data) { + if (!data.empty()) + body_.push_back(BodyItem(data)); + return true; +} + +bool URLRequestInfo::AppendFileToBody(FileRef* file_ref, + int64_t start_offset, + int64_t number_of_bytes, + PP_Time expected_last_modified_time) { + body_.push_back(BodyItem(file_ref, + start_offset, + number_of_bytes, + expected_last_modified_time)); + return true; +} + +WebURLRequest URLRequestInfo::ToWebURLRequest(WebFrame* frame) const { + WebURLRequest web_request; + web_request.initialize(); + web_request.setURL(frame->document().completeURL(WebString::fromUTF8(url_))); + + if (!method_.empty()) + web_request.setHTTPMethod(WebString::fromUTF8(method_)); + + if (!headers_.empty()) { + net::HttpUtil::HeadersIterator it(headers_.begin(), headers_.end(), "\n"); + while (it.GetNext()) { + if (!IsIgnoredRequestHeader(it.name())) { + web_request.addHTTPHeaderField( + WebString::fromUTF8(it.name()), + WebString::fromUTF8(it.values())); + } + } + } + + if (!body_.empty()) { + WebHTTPBody http_body; + http_body.initialize(); + for (size_t i = 0; i < body_.size(); ++i) { + if (body_[i].file_ref) { + WebFileInfo file_info; + file_info.modificationTime = body_[i].expected_last_modified_time; + http_body.appendFileRange( + webkit_glue::FilePathToWebString(body_[i].file_ref->system_path()), + body_[i].start_offset, + body_[i].number_of_bytes, + file_info); + } else { + DCHECK(!body_[i].data.empty()); + http_body.appendData(WebData(body_[i].data)); + } + } + web_request.setHTTPBody(http_body); + } + + frame->setReferrerForRequest(web_request, WebURL()); // Use default. + return web_request; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_url_request_info.h b/webkit/glue/plugins/pepper_url_request_info.h new file mode 100644 index 0000000..ef1452c --- /dev/null +++ b/webkit/glue/plugins/pepper_url_request_info.h @@ -0,0 +1,83 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_URL_REQUEST_INFO_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_URL_REQUEST_INFO_H_ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "third_party/ppapi/c/ppb_url_request_info.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace WebKit { +class WebFrame; +class WebURLRequest; +} + +namespace pepper { + +class URLRequestInfo : public Resource { + public: + explicit URLRequestInfo(PluginModule* module); + virtual ~URLRequestInfo(); + + // Returns a pointer to the interface implementing PPB_URLRequestInfo that is + // exposed to the plugin. + static const PPB_URLRequestInfo* GetInterface(); + + // Resource overrides. + URLRequestInfo* AsURLRequestInfo() { return this; } + + // PPB_URLRequestInfo implementation. + bool SetBooleanProperty(PP_URLRequestProperty property, bool value); + bool SetStringProperty(PP_URLRequestProperty property, + const std::string& value); + bool AppendDataToBody(const std::string& data); + bool AppendFileToBody(FileRef* file_ref, + int64_t start_offset, + int64_t number_of_bytes, + PP_Time expected_last_modified_time); + + WebKit::WebURLRequest ToWebURLRequest(WebKit::WebFrame* frame) const; + + private: + struct BodyItem { + BodyItem(const std::string& data) + : data(data), + start_offset(0), + number_of_bytes(-1), + expected_last_modified_time(0.0) { + } + + BodyItem(FileRef* file_ref, + int64_t start_offset, + int64_t number_of_bytes, + PP_Time expected_last_modified_time) + : file_ref(file_ref), + start_offset(start_offset), + number_of_bytes(number_of_bytes), + expected_last_modified_time(expected_last_modified_time) { + } + + std::string data; + scoped_refptr<FileRef> file_ref; + int64_t start_offset; + int64_t number_of_bytes; + PP_Time expected_last_modified_time; + }; + + typedef std::vector<BodyItem> Body; + + std::string url_; + std::string method_; + std::string headers_; + Body body_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_URL_REQUEST_INFO_H_ diff --git a/webkit/glue/plugins/pepper_url_response_info.cc b/webkit/glue/plugins/pepper_url_response_info.cc new file mode 100644 index 0000000..bff92aa --- /dev/null +++ b/webkit/glue/plugins/pepper_url_response_info.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_url_response_info.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_var.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPHeaderVisitor.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "webkit/glue/plugins/pepper_file_ref.h" +#include "webkit/glue/plugins/pepper_var.h" + +using WebKit::WebHTTPHeaderVisitor; +using WebKit::WebString; +using WebKit::WebURLResponse; + +namespace pepper { + +namespace { + +class HeaderFlattener : public WebHTTPHeaderVisitor { + public: + const std::string& buffer() const { return buffer_; } + + virtual void visitHeader(const WebString& name, const WebString& value) { + if (!buffer_.empty()) + buffer_.append("\n"); + buffer_.append(name.utf8()); + buffer_.append(": "); + buffer_.append(value.utf8()); + } + + private: + std::string buffer_; +}; + +bool IsURLResponseInfo(PP_Resource resource) { + return !!Resource::GetAs<URLResponseInfo>(resource); +} + +PP_Var GetProperty(PP_Resource response_id, + PP_URLResponseProperty property) { + scoped_refptr<URLResponseInfo> response( + Resource::GetAs<URLResponseInfo>(response_id)); + if (!response) + return PP_MakeVoid(); + + return response->GetProperty(property); +} + +PP_Resource GetBody(PP_Resource response_id) { + scoped_refptr<URLResponseInfo> response( + Resource::GetAs<URLResponseInfo>(response_id)); + if (!response.get()) + return 0; + + FileRef* body = response->body(); + if (!body) + return 0; + body->AddRef(); // AddRef for the caller. + + return body->GetReference(); +} + +const PPB_URLResponseInfo ppb_urlresponseinfo = { + &IsURLResponseInfo, + &GetProperty, + &GetBody +}; + +} // namespace + +URLResponseInfo::URLResponseInfo(PluginModule* module) + : Resource(module), + status_code_(-1) { +} + +URLResponseInfo::~URLResponseInfo() { +} + +// static +const PPB_URLResponseInfo* URLResponseInfo::GetInterface() { + return &ppb_urlresponseinfo; +} + +PP_Var URLResponseInfo::GetProperty(PP_URLResponseProperty property) { + switch (property) { + case PP_URLRESPONSEPROPERTY_URL: + return StringToPPVar(url_); + case PP_URLRESPONSEPROPERTY_STATUSCODE: + return PP_MakeInt32(status_code_); + case PP_URLRESPONSEPROPERTY_HEADERS: + return StringToPPVar(headers_); + default: + NOTIMPLEMENTED(); // TODO(darin): Implement me! + return PP_MakeVoid(); + } +} + +bool URLResponseInfo::Initialize(const WebURLResponse& response) { + url_ = response.url().spec(); + status_code_ = response.httpStatusCode(); + + HeaderFlattener flattener; + response.visitHTTPHeaderFields(&flattener); + headers_ = flattener.buffer(); + return true; +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_url_response_info.h b/webkit/glue/plugins/pepper_url_response_info.h new file mode 100644 index 0000000..8874919 --- /dev/null +++ b/webkit/glue/plugins/pepper_url_response_info.h @@ -0,0 +1,47 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_URL_RESPONSE_INFO_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_URL_RESPONSE_INFO_H_ + +#include <string> + +#include "third_party/ppapi/c/ppb_url_response_info.h" +#include "webkit/glue/plugins/pepper_resource.h" + +namespace WebKit { +class WebURLResponse; +} + +namespace pepper { + +class URLResponseInfo : public Resource { + public: + explicit URLResponseInfo(PluginModule* module); + virtual ~URLResponseInfo(); + + // Returns a pointer to the interface implementing PPB_URLResponseInfo that + // is exposed to the plugin. + static const PPB_URLResponseInfo* GetInterface(); + + // Resource overrides. + URLResponseInfo* AsURLResponseInfo() { return this; } + + // PPB_URLResponseInfo implementation. + PP_Var GetProperty(PP_URLResponseProperty property); + + bool Initialize(const WebKit::WebURLResponse& response); + + FileRef* body() { return body_; } + + private: + std::string url_; + std::string headers_; + int32_t status_code_; + scoped_refptr<FileRef> body_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_URL_RESPONSE_INFO_H_ diff --git a/webkit/glue/plugins/pepper_var.cc b/webkit/glue/plugins/pepper_var.cc new file mode 100644 index 0000000..414df7b --- /dev/null +++ b/webkit/glue/plugins/pepper_var.cc @@ -0,0 +1,857 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_var.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "third_party/ppapi/c/pp_var.h" +#include "third_party/ppapi/c/ppb_var.h" +#include "third_party/ppapi/c/ppp_class.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/plugins/pepper_string.h" +#include "v8/include/v8.h" + +using WebKit::WebBindings; + +namespace pepper { + +namespace { + +void Release(PP_Var var); +PP_Var VarFromUtf8(const char* data, uint32_t len); + +// --------------------------------------------------------------------------- +// Exceptions + +class TryCatch { + public: + TryCatch(PP_Var* exception) : exception_(exception) { + WebBindings::pushExceptionHandler(&TryCatch::Catch, this); + } + + ~TryCatch() { + WebBindings::popExceptionHandler(); + } + + bool HasException() const { + return exception_ && exception_->type != PP_VARTYPE_VOID; + } + + void SetException(const char* message) { + DCHECK(!HasException()); + if (exception_) + *exception_ = VarFromUtf8(message, strlen(message)); + } + + private: + static void Catch(void* self, const NPUTF8* message) { + static_cast<TryCatch*>(self)->SetException(message); + } + + // May be null if the consumer isn't interesting in catching exceptions. + PP_Var* exception_; +}; + +const char kInvalidObjectException[] = "Error: Invalid object"; +const char kInvalidPropertyException[] = "Error: Invalid property"; +const char kUnableToGetPropertyException[] = "Error: Unable to get property"; +const char kUnableToSetPropertyException[] = "Error: Unable to set property"; +const char kUnableToRemovePropertyException[] = + "Error: Unable to remove property"; +const char kUnableToGetAllPropertiesException[] = + "Error: Unable to get all properties"; +const char kUnableToCallMethodException[] = "Error: Unable to call method"; +const char kUnableToConstructException[] = "Error: Unable to construct"; + +// --------------------------------------------------------------------------- +// Utilities + +String* GetStringUnchecked(PP_Var var) { + return reinterpret_cast<String*>(var.value.as_id); +} + +NPObject* GetNPObjectUnchecked(PP_Var var) { + return reinterpret_cast<NPObject*>(var.value.as_id); +} + +// Returns a PP_Var that corresponds to the given NPVariant. The contents of +// the NPVariant will be copied unless the NPVariant corresponds to an object. +PP_Var NPVariantToPPVar(const NPVariant* variant) { + switch (variant->type) { + case NPVariantType_Void: + return PP_MakeVoid(); + case NPVariantType_Null: + return PP_MakeNull(); + case NPVariantType_Bool: + return PP_MakeBool(NPVARIANT_TO_BOOLEAN(*variant)); + case NPVariantType_Int32: + return PP_MakeInt32(NPVARIANT_TO_INT32(*variant)); + case NPVariantType_Double: + return PP_MakeDouble(NPVARIANT_TO_DOUBLE(*variant)); + case NPVariantType_String: + return VarFromUtf8(NPVARIANT_TO_STRING(*variant).UTF8Characters, + NPVARIANT_TO_STRING(*variant).UTF8Length); + case NPVariantType_Object: + return NPObjectToPPVar(NPVARIANT_TO_OBJECT(*variant)); + } + NOTREACHED(); + return PP_MakeVoid(); +} + +// Returns a NPVariant that corresponds to the given PP_Var. The contents of +// the PP_Var will be copied unless the PP_Var corresponds to an object. +NPVariant PPVarToNPVariant(PP_Var var) { + NPVariant ret; + switch (var.type) { + case PP_VARTYPE_VOID: + VOID_TO_NPVARIANT(ret); + break; + case PP_VARTYPE_NULL: + NULL_TO_NPVARIANT(ret); + break; + case PP_VARTYPE_BOOL: + BOOLEAN_TO_NPVARIANT(var.value.as_bool, ret); + break; + case PP_VARTYPE_INT32: + INT32_TO_NPVARIANT(var.value.as_int, ret); + break; + case PP_VARTYPE_DOUBLE: + DOUBLE_TO_NPVARIANT(var.value.as_double, ret); + break; + case PP_VARTYPE_STRING: { + const std::string& value = GetStringUnchecked(var)->value(); + STRINGN_TO_NPVARIANT(base::strdup(value.c_str()), value.size(), ret); + break; + } + case PP_VARTYPE_OBJECT: { + NPObject* object = GetNPObjectUnchecked(var); + OBJECT_TO_NPVARIANT(WebBindings::retainObject(object), ret); + break; + } + } + return ret; +} + +// Returns a NPVariant that corresponds to the given PP_Var. The contents of +// the PP_Var will NOT be copied, so you need to ensure that the PP_Var remains +// valid while the resultant NPVariant is in use. +NPVariant PPVarToNPVariantNoCopy(PP_Var var) { + NPVariant ret; + switch (var.type) { + case PP_VARTYPE_VOID: + VOID_TO_NPVARIANT(ret); + break; + case PP_VARTYPE_NULL: + NULL_TO_NPVARIANT(ret); + break; + case PP_VARTYPE_BOOL: + BOOLEAN_TO_NPVARIANT(var.value.as_bool, ret); + break; + case PP_VARTYPE_INT32: + INT32_TO_NPVARIANT(var.value.as_int, ret); + break; + case PP_VARTYPE_DOUBLE: + DOUBLE_TO_NPVARIANT(var.value.as_double, ret); + break; + case PP_VARTYPE_STRING: { + const std::string& value = GetStringUnchecked(var)->value(); + STRINGN_TO_NPVARIANT(value.c_str(), value.size(), ret); + break; + } + case PP_VARTYPE_OBJECT: { + OBJECT_TO_NPVARIANT(GetNPObjectUnchecked(var), ret); + break; + } + } + return ret; +} + +// Returns a NPIdentifier that corresponds to the given PP_Var. The contents +// of the PP_Var will be copied. Returns NULL if the given PP_Var is not a a +// string or integer type. +NPIdentifier PPVarToNPIdentifier(PP_Var var) { + switch (var.type) { + case PP_VARTYPE_STRING: + return WebBindings::getStringIdentifier( + GetStringUnchecked(var)->value().c_str()); + case PP_VARTYPE_INT32: + return WebBindings::getIntIdentifier(var.value.as_int); + default: + return NULL; + } +} + +PP_Var NPIdentifierToPPVar(NPIdentifier id) { + const NPUTF8* string_value = NULL; + int32_t int_value = 0; + bool is_string = false; + WebBindings::extractIdentifierData(id, string_value, int_value, is_string); + if (is_string) + return VarFromUtf8(string_value, strlen(string_value)); + + return PP_MakeInt32(int_value); +} + +PP_Var NPIdentifierToPPVarString(NPIdentifier id) { + PP_Var var = NPIdentifierToPPVar(id); + if (var.type == PP_VARTYPE_STRING) + return var; + DCHECK(var.type == PP_VARTYPE_INT32); + const std::string& str = IntToString(var.value.as_int); + return VarFromUtf8(str.data(), str.size()); +} + +void ThrowException(NPObject* object, PP_Var exception) { + String* str = GetString(exception); + if (str) + WebBindings::setException(object, str->value().c_str()); +} + +// --------------------------------------------------------------------------- +// NPObject implementation in terms of PPP_Class + +struct WrapperObject : NPObject { + const PPP_Class* ppp_class; + void* ppp_class_data; +}; + +static WrapperObject* ToWrapper(NPObject* object) { + return static_cast<WrapperObject*>(object); +} + +NPObject* WrapperClass_Allocate(NPP npp, NPClass* unused) { + return new WrapperObject; +} + +void WrapperClass_Deallocate(NPObject* object) { + WrapperObject* wrapper = ToWrapper(object); + wrapper->ppp_class->Deallocate(wrapper->ppp_class_data); + delete object; +} + +void WrapperClass_Invalidate(NPObject* object) { + // TODO(darin): Do I need to do something here? +} + +bool WrapperClass_HasMethod(NPObject* object, NPIdentifier method_name) { + WrapperObject* wrapper = ToWrapper(object); + + PP_Var method_name_var = NPIdentifierToPPVarString(method_name); + PP_Var exception = PP_MakeVoid(); + bool rv = wrapper->ppp_class->HasMethod(wrapper->ppp_class_data, + method_name_var, + &exception); + Release(method_name_var); + + if (exception.type != PP_VARTYPE_VOID) { + ThrowException(object, exception); + Release(exception); + return false; + } + return rv; +} + +bool WrapperClass_Invoke(NPObject* object, NPIdentifier method_name, + const NPVariant* argv, uint32_t argc, + NPVariant* result) { + WrapperObject* wrapper = ToWrapper(object); + + scoped_array<PP_Var> args; + if (argc) { + args.reset(new PP_Var[argc]); + for (uint32_t i = 0; i < argc; ++i) + args[i] = NPVariantToPPVar(&argv[i]); + } + PP_Var method_name_var = NPIdentifierToPPVarString(method_name); + PP_Var exception = PP_MakeVoid(); + PP_Var result_var = wrapper->ppp_class->Call(wrapper->ppp_class_data, + method_name_var, argc, + args.get(), &exception); + Release(method_name_var); + for (uint32_t i = 0; i < argc; ++i) + Release(args[i]); + + bool rv; + if (exception.type == PP_VARTYPE_VOID) { + rv = true; + *result = PPVarToNPVariant(result_var); + } else { + rv = false; + ThrowException(object, exception); + Release(exception); + } + Release(result_var); + return rv; +} + +bool WrapperClass_InvokeDefault(NPObject* object, const NPVariant* argv, + uint32_t argc, NPVariant* result) { + WrapperObject* wrapper = ToWrapper(object); + + scoped_array<PP_Var> args; + if (argc) { + args.reset(new PP_Var[argc]); + for (uint32_t i = 0; i < argc; ++i) + args[i] = NPVariantToPPVar(&argv[i]); + } + PP_Var exception = PP_MakeVoid(); + PP_Var result_var = wrapper->ppp_class->Call(wrapper->ppp_class_data, + PP_MakeVoid(), argc, args.get(), + &exception); + for (uint32_t i = 0; i < argc; ++i) + Release(args[i]); + + bool rv; + if (exception.type == PP_VARTYPE_VOID) { + rv = true; + *result = PPVarToNPVariant(result_var); + } else { + rv = false; + ThrowException(object, exception); + Release(exception); + } + Release(result_var); + return rv; +} + +bool WrapperClass_HasProperty(NPObject* object, NPIdentifier property_name) { + WrapperObject* wrapper = ToWrapper(object); + + PP_Var property_name_var = NPIdentifierToPPVar(property_name); + PP_Var exception = PP_MakeVoid(); + bool rv = wrapper->ppp_class->HasProperty(wrapper->ppp_class_data, + property_name_var, + &exception); + Release(property_name_var); + + if (exception.type != PP_VARTYPE_VOID) { + ThrowException(object, exception); + Release(exception); + return false; + } + return rv; +} + +bool WrapperClass_GetProperty(NPObject* object, NPIdentifier property_name, + NPVariant* result) { + WrapperObject* wrapper = ToWrapper(object); + + PP_Var property_name_var = NPIdentifierToPPVar(property_name); + PP_Var exception = PP_MakeVoid(); + PP_Var result_var = wrapper->ppp_class->GetProperty(wrapper->ppp_class_data, + property_name_var, + &exception); + Release(property_name_var); + + bool rv; + if (exception.type == PP_VARTYPE_VOID) { + rv = true; + *result = PPVarToNPVariant(result_var); + } else { + rv = false; + ThrowException(object, exception); + Release(exception); + } + Release(result_var); + return rv; +} + +bool WrapperClass_SetProperty(NPObject* object, NPIdentifier property_name, + const NPVariant* value) { + WrapperObject* wrapper = ToWrapper(object); + + PP_Var property_name_var = NPIdentifierToPPVar(property_name); + PP_Var value_var = NPVariantToPPVar(value); + PP_Var exception = PP_MakeVoid(); + wrapper->ppp_class->SetProperty(wrapper->ppp_class_data, property_name_var, + value_var, &exception); + Release(value_var); + Release(property_name_var); + + if (exception.type != PP_VARTYPE_VOID) { + ThrowException(object, exception); + Release(exception); + return false; + } + return true; +} + +bool WrapperClass_RemoveProperty(NPObject* object, NPIdentifier property_name) { + WrapperObject* wrapper = ToWrapper(object); + + PP_Var property_name_var = NPIdentifierToPPVar(property_name); + PP_Var exception = PP_MakeVoid(); + wrapper->ppp_class->RemoveProperty(wrapper->ppp_class_data, property_name_var, + &exception); + Release(property_name_var); + + if (exception.type != PP_VARTYPE_VOID) { + ThrowException(object, exception); + Release(exception); + return false; + } + return true; +} + +bool WrapperClass_Enumerate(NPObject* object, NPIdentifier** values, + uint32_t* count) { + WrapperObject* wrapper = ToWrapper(object); + + uint32_t property_count = 0; + PP_Var* properties = NULL; + PP_Var exception = PP_MakeVoid(); + wrapper->ppp_class->GetAllPropertyNames(wrapper->ppp_class_data, + &property_count, + &properties, + &exception); + + bool rv; + if (exception.type == PP_VARTYPE_VOID) { + rv = true; + if (property_count == 0) { + *values = NULL; + *count = 0; + } else { + *values = static_cast<NPIdentifier*>( + malloc(sizeof(NPIdentifier) * property_count)); + *count = property_count; + for (uint32_t i = 0; i < property_count; ++i) + (*values)[i] = PPVarToNPIdentifier(properties[i]); + } + } else { + rv = false; + ThrowException(object, exception); + Release(exception); + } + + for (uint32_t i = 0; i < property_count; ++i) + Release(properties[i]); + free(properties); + return rv; +} + +bool WrapperClass_Construct(NPObject* object, const NPVariant* argv, + uint32_t argc, NPVariant* result) { + WrapperObject* wrapper = ToWrapper(object); + + scoped_array<PP_Var> args; + if (argc) { + args.reset(new PP_Var[argc]); + for (uint32_t i = 0; i < argc; ++i) + args[i] = NPVariantToPPVar(&argv[i]); + } + + PP_Var exception = PP_MakeVoid(); + PP_Var result_var = wrapper->ppp_class->Construct(wrapper->ppp_class_data, + argc, args.get(), + &exception); + for (uint32_t i = 0; i < argc; ++i) + Release(args[i]); + + bool rv; + if (exception.type == PP_VARTYPE_VOID) { + rv = true; + *result = PPVarToNPVariant(result_var); + } else { + rv = false; + ThrowException(object, exception); + Release(exception); + } + Release(result_var); + return rv; +} + +const NPClass wrapper_class = { + NP_CLASS_STRUCT_VERSION, + WrapperClass_Allocate, + WrapperClass_Deallocate, + WrapperClass_Invalidate, + WrapperClass_HasMethod, + WrapperClass_Invoke, + WrapperClass_InvokeDefault, + WrapperClass_HasProperty, + WrapperClass_GetProperty, + WrapperClass_SetProperty, + WrapperClass_RemoveProperty, + WrapperClass_Enumerate, + WrapperClass_Construct +}; + +// --------------------------------------------------------------------------- +// PPB_Var methods + +void AddRef(PP_Var var) { + if (var.type == PP_VARTYPE_STRING) { + GetStringUnchecked(var)->AddRef(); + } else if (var.type == PP_VARTYPE_OBJECT) { + // TODO(darin): Add thread safety check + WebBindings::retainObject(GetNPObjectUnchecked(var)); + } +} + +void Release(PP_Var var) { + if (var.type == PP_VARTYPE_STRING) { + GetStringUnchecked(var)->Release(); + } else if (var.type == PP_VARTYPE_OBJECT) { + // TODO(darin): Add thread safety check + WebBindings::releaseObject(GetNPObjectUnchecked(var)); + } +} + +PP_Var VarFromUtf8(const char* data, uint32_t len) { + String* str = new String(data, len); + str->AddRef(); // This is for the caller, we return w/ a refcount of 1. + PP_Var ret; + ret.type = PP_VARTYPE_STRING; + ret.value.as_id = reinterpret_cast<intptr_t>(str); + return ret; +} + +const char* VarToUtf8(PP_Var var, uint32_t* len) { + if (var.type != PP_VARTYPE_STRING) { + *len = 0; + return NULL; + } + const std::string& str = GetStringUnchecked(var)->value(); + *len = static_cast<uint32_t>(str.size()); + if (str.empty()) + return ""; // Don't return NULL on success. + return str.data(); +} + +bool HasProperty(PP_Var var, + PP_Var name, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return false; + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return false; + } + + NPIdentifier identifier = PPVarToNPIdentifier(name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return false; + } + + return WebBindings::hasProperty(NULL, object, identifier); +} + +bool HasMethod(PP_Var var, + PP_Var name, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return false; + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return false; + } + + NPIdentifier identifier = PPVarToNPIdentifier(name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return false; + } + + return WebBindings::hasMethod(NULL, object, identifier); +} + +PP_Var GetProperty(PP_Var var, + PP_Var name, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return PP_MakeVoid(); + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return PP_MakeVoid(); + } + + NPIdentifier identifier = PPVarToNPIdentifier(name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return PP_MakeVoid(); + } + + NPVariant result; + if (!WebBindings::getProperty(NULL, object, identifier, &result)) { + // An exception may have been raised. + if (!try_catch.HasException()) + try_catch.SetException(kUnableToGetPropertyException); + return PP_MakeVoid(); + } + + PP_Var ret = NPVariantToPPVar(&result); + WebBindings::releaseVariantValue(&result); + return ret; +} + +void GetAllPropertyNames(PP_Var var, + uint32_t* property_count, + PP_Var** properties, + PP_Var* exception) { + *properties = NULL; + *property_count = 0; + + TryCatch try_catch(exception); + if (try_catch.HasException()) + return; + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return; + } + + NPIdentifier* identifiers = NULL; + uint32_t count = 0; + if (!WebBindings::enumerate(NULL, object, &identifiers, &count)) { + if (!try_catch.HasException()) + try_catch.SetException(kUnableToGetAllPropertiesException); + return; + } + + if (count == 0) + return; + + *property_count = count; + *properties = static_cast<PP_Var*>(malloc(sizeof(PP_Var) * count)); + for (uint32_t i = 0; i < count; ++i) + (*properties)[i] = NPIdentifierToPPVar(identifiers[i]); + free(identifiers); +} + +void SetProperty(PP_Var var, + PP_Var name, + PP_Var value, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return; + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return; + } + + NPIdentifier identifier = PPVarToNPIdentifier(name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return; + } + + NPVariant variant = PPVarToNPVariantNoCopy(value); + if (!WebBindings::setProperty(NULL, object, identifier, &variant)) { + if (!try_catch.HasException()) + try_catch.SetException(kUnableToSetPropertyException); + } +} + +void RemoveProperty(PP_Var var, + PP_Var name, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return; + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return; + } + + NPIdentifier identifier = PPVarToNPIdentifier(name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return; + } + + if (!WebBindings::removeProperty(NULL, object, identifier)) { + if (!try_catch.HasException()) + try_catch.SetException(kUnableToRemovePropertyException); + } +} + +PP_Var Call(PP_Var var, + PP_Var method_name, + uint32_t argc, + PP_Var* argv, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return PP_MakeVoid(); + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return PP_MakeVoid(); + } + + NPIdentifier identifier; + if (method_name.type == PP_VARTYPE_VOID) { + identifier = NULL; + } else if (method_name.type == PP_VARTYPE_STRING) { + // Specifically allow only string functions to be called. + identifier = PPVarToNPIdentifier(method_name); + if (!identifier) { + try_catch.SetException(kInvalidPropertyException); + return PP_MakeVoid(); + } + } else { + try_catch.SetException(kInvalidPropertyException); + return PP_MakeVoid(); + } + + scoped_array<NPVariant> args; + if (argc) { + args.reset(new NPVariant[argc]); + for (uint32_t i = 0; i < argc; ++i) + args[i] = PPVarToNPVariantNoCopy(argv[i]); + } + + bool ok; + + NPVariant result; + if (identifier) { + ok = WebBindings::invoke(NULL, object, identifier, args.get(), argc, + &result); + } else { + ok = WebBindings::invokeDefault(NULL, object, args.get(), argc, &result); + } + + if (!ok) { + // An exception may have been raised. + if (!try_catch.HasException()) + try_catch.SetException(kUnableToCallMethodException); + return PP_MakeVoid(); + } + + PP_Var ret = NPVariantToPPVar(&result); + WebBindings::releaseVariantValue(&result); + return ret; +} + +PP_Var Construct(PP_Var var, + uint32_t argc, + PP_Var* argv, + PP_Var* exception) { + TryCatch try_catch(exception); + if (try_catch.HasException()) + return PP_MakeVoid(); + + NPObject* object = GetNPObject(var); + if (!object) { + try_catch.SetException(kInvalidObjectException); + return PP_MakeVoid(); + } + + scoped_array<NPVariant> args; + if (argc) { + args.reset(new NPVariant[argc]); + for (uint32_t i = 0; i < argc; ++i) + args[i] = PPVarToNPVariantNoCopy(argv[i]); + } + + NPVariant result; + if (!WebBindings::construct(NULL, object, args.get(), argc, &result)) { + // An exception may have been raised. + if (!try_catch.HasException()) + try_catch.SetException(kUnableToConstructException); + return PP_MakeVoid(); + } + + PP_Var ret = NPVariantToPPVar(&result); + WebBindings::releaseVariantValue(&result); + return ret; +} + +bool IsInstanceOf(PP_Var var, const PPP_Class* ppp_class, + void** ppp_class_data) { + NPObject* object = GetNPObject(var); + if (!object) + return false; + + if (object->_class != &wrapper_class) + return false; + + WrapperObject* wrapper = ToWrapper(object); + if (wrapper->ppp_class != ppp_class) + return false; + + if (ppp_class_data) + *ppp_class_data = wrapper->ppp_class_data; + return true; +} + +PP_Var CreateObject(const PPP_Class* ppp_class, void* ppp_class_data) { + NPObject* object = + WebBindings::createObject(NULL, const_cast<NPClass*>(&wrapper_class)); + static_cast<WrapperObject*>(object)->ppp_class = ppp_class; + static_cast<WrapperObject*>(object)->ppp_class_data = ppp_class_data; + PP_Var ret = NPObjectToPPVar(object); + WebBindings::releaseObject(object); // Release reference from createObject + return ret; +} + +const PPB_Var var_interface = { + &AddRef, + &Release, + &VarFromUtf8, + &VarToUtf8, + &HasProperty, + &HasMethod, + &GetProperty, + &GetAllPropertyNames, + &SetProperty, + &RemoveProperty, + &Call, + &Construct, + &IsInstanceOf, + &CreateObject +}; + +} // namespace + +const PPB_Var* GetVarInterface() { + return &var_interface; +} + +PP_Var NPObjectToPPVar(NPObject* object) { + PP_Var ret; + ret.type = PP_VARTYPE_OBJECT; + ret.value.as_id = reinterpret_cast<intptr_t>(object); + WebBindings::retainObject(object); + return ret; +} + +NPObject* GetNPObject(PP_Var var) { + if (var.type != PP_VARTYPE_OBJECT) + return NULL; + return GetNPObjectUnchecked(var); +} + +PP_Var StringToPPVar(const std::string& str) { + DCHECK(IsStringUTF8(str)); + return VarFromUtf8(str.data(), str.size()); +} + +String* GetString(PP_Var var) { + if (var.type != PP_VARTYPE_STRING) + return NULL; + return GetStringUnchecked(var); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_var.h b/webkit/glue/plugins/pepper_var.h new file mode 100644 index 0000000..b8c31cc --- /dev/null +++ b/webkit/glue/plugins/pepper_var.h @@ -0,0 +1,46 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_VAR_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_VAR_H_ + +#include <string> + +typedef struct _pp_Var PP_Var; +typedef struct _ppb_Var PPB_Var; +typedef struct NPObject NPObject; +typedef struct _NPVariant NPVariant; +typedef void* NPIdentifier; + +namespace pepper { + +class String; + +// There's no class implementing Var since it could represent a number of +// objects. Instead, we just expose a getter for the interface implemented in +// the .cc file here. +const PPB_Var* GetVarInterface(); + +// Returns a PP_Var of type object that wraps the given NPObject. Calling this +// function multiple times given the same NPObject results in the same PP_Var. +PP_Var NPObjectToPPVar(NPObject* object); + +// Returns the NPObject corresponding to the PP_Var. This pointer has not been +// retained, so you should not call WebBindings::releaseObject unless you first +// call WebBindings::retainObject. Returns NULL if the PP_Var is not an object +// type. +NPObject* GetNPObject(PP_Var var); + +// Returns a PP_Var of type string that contains a copy of the given string. +// The input data must be valid UTF-8 encoded text. +PP_Var StringToPPVar(const std::string& str); + +// Returns the String corresponding to the PP_Var. This pointer has not been +// AddRef'd, so you should not call Release! Returns NULL if the PP_Var is not +// a string type. +String* GetString(PP_Var var); + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_VAR_H_ diff --git a/webkit/glue/plugins/pepper_webplugin_impl.cc b/webkit/glue/plugins/pepper_webplugin_impl.cc new file mode 100644 index 0000000..df8aae0 --- /dev/null +++ b/webkit/glue/plugins/pepper_webplugin_impl.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_webplugin_impl.h" + +#include "base/file_path.h" +#include "base/message_loop.h" +#include "third_party/ppapi/c/pp_var.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginParams.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" +#include "webkit/glue/plugins/pepper_url_loader.h" +#include "webkit/glue/plugins/pepper_var.h" + +using WebKit::WebCanvas; +using WebKit::WebPluginContainer; +using WebKit::WebPluginParams; +using WebKit::WebRect; +using WebKit::WebString; +using WebKit::WebVector; + +namespace pepper { + +WebPluginImpl::WebPluginImpl( + PluginModule* plugin_module, + const WebPluginParams& params, + const base::WeakPtr<PluginDelegate>& plugin_delegate) + : init_data_(new InitData()), + full_frame_(params.loadManually) { + DCHECK(plugin_module); + init_data_->module = plugin_module; + init_data_->delegate = plugin_delegate; + for (size_t i = 0; i < params.attributeNames.size(); ++i) { + init_data_->arg_names.push_back(params.attributeNames[i].utf8()); + init_data_->arg_values.push_back(params.attributeValues[i].utf8()); + } +} + +WebPluginImpl::~WebPluginImpl() { +} + +bool WebPluginImpl::initialize(WebPluginContainer* container) { + // The plugin delegate may have gone away. + if (!init_data_->delegate) + return false; + + instance_ = init_data_->module->CreateInstance(init_data_->delegate); + if (!instance_) + return false; + + bool success = instance_->Initialize(container, + init_data_->arg_names, + init_data_->arg_values, + full_frame_); + if (!success) { + instance_->Delete(); + instance_ = NULL; + return false; + } + + init_data_.reset(); + return true; +} + +void WebPluginImpl::destroy() { + if (instance_) { + instance_->Delete(); + instance_ = NULL; + } + + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +NPObject* WebPluginImpl::scriptableObject() { + return GetNPObject(instance_->GetInstanceObject()); +} + +void WebPluginImpl::paint(WebCanvas* canvas, const WebRect& rect) { + instance_->Paint(canvas, plugin_rect_, rect); +} + +void WebPluginImpl::updateGeometry( + const WebRect& window_rect, + const WebRect& clip_rect, + const WebVector<WebRect>& cut_outs_rects, + bool is_visible) { + plugin_rect_ = window_rect; + instance_->ViewChanged(plugin_rect_, clip_rect); +} + +void WebPluginImpl::updateFocus(bool focused) { +} + +void WebPluginImpl::updateVisibility(bool visible) { +} + +bool WebPluginImpl::acceptsInputEvents() { + return true; +} + +bool WebPluginImpl::handleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo& cursor_info) { + return instance_->HandleInputEvent(event, &cursor_info); +} + +void WebPluginImpl::didReceiveResponse( + const WebKit::WebURLResponse& response) { + DCHECK(!document_loader_); + + document_loader_ = new URLLoader(instance_); + document_loader_->didReceiveResponse(NULL, response); + + if (!instance_->HandleDocumentLoad(document_loader_)) + document_loader_ = NULL; +} + +void WebPluginImpl::didReceiveData(const char* data, int data_length) { + if (document_loader_) + document_loader_->didReceiveData(NULL, data, data_length); +} + +void WebPluginImpl::didFinishLoading() { + if (document_loader_) { + document_loader_->didFinishLoading(NULL); + document_loader_ = NULL; + } +} + +void WebPluginImpl::didFailLoading(const WebKit::WebURLError& error) { + if (document_loader_) { + document_loader_->didFail(NULL, error); + document_loader_ = NULL; + } +} + +void WebPluginImpl::didFinishLoadingFrameRequest(const WebKit::WebURL& url, + void* notify_data) { +} + +void WebPluginImpl::didFailLoadingFrameRequest( + const WebKit::WebURL& url, + void* notify_data, + const WebKit::WebURLError& error) { +} + +bool WebPluginImpl::hasSelection() const { + return !selectionAsText().isEmpty(); +} + +WebKit::WebString WebPluginImpl::selectionAsText() const { + return instance_->GetSelectedText(false); +} + +WebKit::WebString WebPluginImpl::selectionAsMarkup() const { + return instance_->GetSelectedText(true); +} + +void WebPluginImpl::setZoomFactor(float scale, bool text_only) { + instance_->Zoom(scale, text_only); +} + +bool WebPluginImpl::startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier) { + return instance_->StartFind(search_text, case_sensitive, identifier); +} + +void WebPluginImpl::selectFindResult(bool forward) { + instance_->SelectFindResult(forward); +} + +void WebPluginImpl::stopFind() { + instance_->StopFind(); +} + +bool WebPluginImpl::supportsPaginatedPrint() { + return instance_->SupportsPrintInterface(); +} + +int WebPluginImpl::printBegin(const WebKit::WebRect& printable_area, + int printer_dpi) { + return instance_->PrintBegin(printable_area, printer_dpi); +} + +bool WebPluginImpl::printPage(int page_number, + WebKit::WebCanvas* canvas) { + return instance_->PrintPage(page_number, canvas); +} + +void WebPluginImpl::printEnd() { + return instance_->PrintEnd(); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_webplugin_impl.h b/webkit/glue/plugins/pepper_webplugin_impl.h new file mode 100644 index 0000000..9d2f313 --- /dev/null +++ b/webkit/glue/plugins/pepper_webplugin_impl.h @@ -0,0 +1,98 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_WEBPLUGIN_IMPL_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_WEBPLUGIN_IMPL_H_ + +#include <string> +#include <vector> + +#include "base/weak_ptr.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "gfx/rect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPlugin.h" + +namespace WebKit { +struct WebPluginParams; +} + +namespace pepper { + +class PluginDelegate; +class PluginInstance; +class PluginModule; +class URLLoader; + +class WebPluginImpl : public WebKit::WebPlugin { + public: + WebPluginImpl(PluginModule* module, + const WebKit::WebPluginParams& params, + const base::WeakPtr<PluginDelegate>& plugin_delegate); + + private: + friend class DeleteTask<WebPluginImpl>; + + ~WebPluginImpl(); + + // WebKit::WebPlugin implementation. + virtual bool initialize(WebKit::WebPluginContainer* container); + virtual void destroy(); + virtual NPObject* scriptableObject(); + virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect); + virtual void updateGeometry( + const WebKit::WebRect& frame_rect, + const WebKit::WebRect& clip_rect, + const WebKit::WebVector<WebKit::WebRect>& cut_outs_rects, + bool is_visible); + virtual void updateFocus(bool focused); + virtual void updateVisibility(bool visible); + virtual bool acceptsInputEvents(); + virtual bool handleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo& cursor_info); + virtual void didReceiveResponse(const WebKit::WebURLResponse& response); + virtual void didReceiveData(const char* data, int data_length); + virtual void didFinishLoading(); + virtual void didFailLoading(const WebKit::WebURLError&); + virtual void didFinishLoadingFrameRequest(const WebKit::WebURL& url, + void* notify_data); + virtual void didFailLoadingFrameRequest(const WebKit::WebURL& url, + void* notify_data, + const WebKit::WebURLError& error); + virtual bool hasSelection() const; + virtual WebKit::WebString selectionAsText() const; + virtual WebKit::WebString selectionAsMarkup() const; + virtual void setZoomFactor(float scale, bool text_only); + virtual bool startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier); + virtual void selectFindResult(bool forward); + virtual void stopFind(); + virtual bool supportsPaginatedPrint(); + virtual int printBegin(const WebKit::WebRect& printable_area, + int printer_dpi); + virtual bool printPage(int page_number, WebKit::WebCanvas* canvas); + virtual void printEnd(); + + struct InitData { + scoped_refptr<PluginModule> module; + base::WeakPtr<PluginDelegate> delegate; + std::vector<std::string> arg_names; + std::vector<std::string> arg_values; + }; + + scoped_ptr<InitData> init_data_; // Cleared upon successful initialization. + // True if the instance represents the entire document in a frame instead of + // being an embedded resource. + bool full_frame_; + scoped_refptr<PluginInstance> instance_; + scoped_refptr<URLLoader> document_loader_; + gfx::Rect plugin_rect_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginImpl); +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_WEBPLUGIN_IMPL_H_ diff --git a/webkit/glue/plugins/pepper_widget.cc b/webkit/glue/plugins/pepper_widget.cc new file mode 100644 index 0000000..74b0e40 --- /dev/null +++ b/webkit/glue/plugins/pepper_widget.cc @@ -0,0 +1,91 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/pepper_widget.h" + +#include "base/logging.h" +#include "third_party/ppapi/c/pp_completion_callback.h" +#include "third_party/ppapi/c/pp_errors.h" +#include "third_party/ppapi/c/ppb_widget.h" +#include "third_party/ppapi/c/ppp_widget.h" +#include "webkit/glue/plugins/pepper_image_data.h" +#include "webkit/glue/plugins/pepper_plugin_instance.h" +#include "webkit/glue/plugins/pepper_plugin_module.h" + +namespace pepper { + +namespace { + +bool IsWidget(PP_Resource resource) { + return !!Resource::GetAs<Widget>(resource); +} + +bool Paint(PP_Resource resource, const PP_Rect* rect, PP_Resource image_id) { + scoped_refptr<Widget> widget(Resource::GetAs<Widget>(resource)); + if (!widget) + return false; + + scoped_refptr<ImageData> image(Resource::GetAs<ImageData>(image_id)); + return widget && widget->Paint(rect, image); +} + +bool HandleEvent(PP_Resource resource, const PP_Event* event) { + scoped_refptr<Widget> widget(Resource::GetAs<Widget>(resource)); + return widget && widget->HandleEvent(event); +} + +bool GetLocation(PP_Resource resource, PP_Rect* location) { + scoped_refptr<Widget> widget(Resource::GetAs<Widget>(resource)); + return widget && widget->GetLocation(location); +} + +void SetLocation(PP_Resource resource, const PP_Rect* location) { + scoped_refptr<Widget> widget(Resource::GetAs<Widget>(resource)); + if (widget) + widget->SetLocation(location); +} + +const PPB_Widget ppb_widget = { + &IsWidget, + &Paint, + &HandleEvent, + &GetLocation, + &SetLocation, +}; + +} // namespace + +Widget::Widget(PluginInstance* instance) + : Resource(instance->module()), + instance_(instance) { +} + +Widget::~Widget() { +} + +// static +const PPB_Widget* Widget::GetInterface() { + return &ppb_widget; +} + +bool Widget::GetLocation(PP_Rect* location) { + *location = location_; + return true; +} + +void Widget::SetLocation(const PP_Rect* location) { + location_ = *location; + SetLocationInternal(location); +} + +void Widget::Invalidate(const PP_Rect* dirty) { + const PPP_Widget* widget = static_cast<const PPP_Widget*>( + module()->GetPluginInterface(PPP_WIDGET_INTERFACE)); + if (!widget) + return; + ScopedResourceId resource(this); + widget->Invalidate(instance_->GetPPInstance(), resource.id, dirty); +} + +} // namespace pepper diff --git a/webkit/glue/plugins/pepper_widget.h b/webkit/glue/plugins/pepper_widget.h new file mode 100644 index 0000000..048a718 --- /dev/null +++ b/webkit/glue/plugins/pepper_widget.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PEPPER_WIDGET_H_ +#define WEBKIT_GLUE_PLUGINS_PEPPER_WIDGET_H_ + +#include "base/scoped_ptr.h" +#include "third_party/ppapi/c/pp_rect.h" +#include "webkit/glue/plugins/pepper_resource.h" + +typedef struct _ppb_Widget PPB_Widget; +typedef struct _pp_Event PP_Event; + +namespace pepper { + +class ImageData; +class PluginInstance; + +class Widget : public Resource { + public: + explicit Widget(PluginInstance* instance); + virtual ~Widget(); + + // Returns a pointer to the interface implementing PPB_Widget that is + // exposed to the plugin. + static const PPB_Widget* GetInterface(); + + // Resource overrides. + Widget* AsWidget() { return this; } + + // PPB_Widget implementation. + virtual bool Paint(const PP_Rect* rect, ImageData* image) = 0; + virtual bool HandleEvent(const PP_Event* event) = 0; + bool GetLocation(PP_Rect* location); + void SetLocation(const PP_Rect* location); + + // Notifies the plugin instance that the given rect needs to be repainted. + void Invalidate(const PP_Rect* dirty); + PluginInstance* instance() { return instance_; } + + protected: + virtual void SetLocationInternal(const PP_Rect* location) = 0; + PP_Rect location() const { return location_; } + + private: + scoped_refptr<PluginInstance> instance_; + PP_Rect location_; +}; + +} // namespace pepper + +#endif // WEBKIT_GLUE_PLUGINS_PEPPER_WIDGET_H_ diff --git a/webkit/glue/plugins/plugin_constants_win.h b/webkit/glue/plugins/plugin_constants_win.h new file mode 100644 index 0000000..9913e5d --- /dev/null +++ b/webkit/glue/plugins/plugin_constants_win.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGIN_CONSTANTS_WIN_H_ +#define WEBKIT_GLUE_PLUGIN_CONSTANTS_WIN_H_ + +// Used by the plugins_test when testing the older WMP plugin to force the new +// plugin to not get loaded. +#define kUseOldWMPPluginSwitch "use-old-wmp" + +// The window class name for a plugin window. +#define kNativeWindowClassName L"NativeWindowClass" + +// The name of the window class name for the wrapper HWND around the actual +// plugin window that's used when running in multi-process mode. This window +// is created on the browser UI thread. +#define kWrapperNativeWindowClassName L"WrapperNativeWindowClass" + +// The name of the custom window message that the browser uses to tell the +// plugin process to paint a window. +#define kPaintMessageName L"Chrome_CustomPaint" + +// The name of the registry key which NPAPI plugins update on installation. +#define kRegistryMozillaPlugins L"SOFTWARE\\MozillaPlugins" + +#define kMozillaActiveXPlugin L"npmozax.dll" +#define kNewWMPPlugin L"np-mswmp.dll" +#define kOldWMPPlugin L"npdsplay.dll" +#define kYahooApplicationStatePlugin L"npystate.dll" +#define kWanWangProtocolHandlerPlugin L"npww.dll" +#define kFlashPlugin L"npswf32.dll" +#define kAcrobatReaderPlugin L"nppdf32.dll" +#define kRealPlayerPlugin L"nppl3260.dll" +#define kSilverlightPlugin L"npctrl.dll" +#define kJavaPlugin1 L"npjp2.dll" +#define kJavaPlugin2 L"npdeploytk.dll" + +#define kGPUPluginMimeType "application/vnd.google.chrome.gpu-plugin" + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_LIST_H_ diff --git a/webkit/glue/plugins/plugin_host.cc b/webkit/glue/plugins/plugin_host.cc new file mode 100644 index 0000000..fe1d7ef --- /dev/null +++ b/webkit/glue/plugins/plugin_host.cc @@ -0,0 +1,1088 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_host.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#if defined(OS_MACOSX) +#include "base/sys_info.h" +#endif +#include "base/sys_string_conversions.h" +#include "net/base/net_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebBindings.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/default_plugin_shared.h" +#include "webkit/glue/plugins/npapi_extension_thunk.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/plugins/webplugininfo.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/npapi/bindings/npruntime.h" + +using WebKit::WebBindings; + +// Finds a PluginInstance from an NPP. +// The caller must take a reference if needed. +static NPAPI::PluginInstance* FindInstance(NPP id) { + if (id == NULL) { + NOTREACHED(); + return NULL; + } + return reinterpret_cast<NPAPI::PluginInstance*>(id->ndata); +} + +#if defined(OS_MACOSX) +// Returns true if the OS supports shared accelerated surfaces via IOSurface. +// This is true on Snow Leopard and higher. +static bool SupportsSharingAcceleratedSurfaces() { + int32 major, minor, bugfix; + base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix); + return major > 10 || (major == 10 && minor > 5); +} +#endif + +namespace NPAPI { + +scoped_refptr<PluginHost> PluginHost::singleton_; + +PluginHost::PluginHost() { + InitializeHostFuncs(); +} + +PluginHost::~PluginHost() { +} + +PluginHost *PluginHost::Singleton() { + if (singleton_.get() == NULL) { + singleton_ = new PluginHost(); + } + + DCHECK(singleton_.get() != NULL); + return singleton_; +} + +void PluginHost::InitializeHostFuncs() { + memset(&host_funcs_, 0, sizeof(host_funcs_)); + host_funcs_.size = sizeof(host_funcs_); + host_funcs_.version = (NP_VERSION_MAJOR << 8) | (NP_VERSION_MINOR); + + // The "basic" functions + host_funcs_.geturl = &NPN_GetURL; + host_funcs_.posturl = &NPN_PostURL; + host_funcs_.requestread = &NPN_RequestRead; + host_funcs_.newstream = &NPN_NewStream; + host_funcs_.write = &NPN_Write; + host_funcs_.destroystream = &NPN_DestroyStream; + host_funcs_.status = &NPN_Status; + host_funcs_.uagent = &NPN_UserAgent; + host_funcs_.memalloc = &NPN_MemAlloc; + host_funcs_.memfree = &NPN_MemFree; + host_funcs_.memflush = &NPN_MemFlush; + host_funcs_.reloadplugins = &NPN_ReloadPlugins; + + // We don't implement java yet + host_funcs_.getJavaEnv = &NPN_GetJavaEnv; + host_funcs_.getJavaPeer = &NPN_GetJavaPeer; + + // Advanced functions we implement + host_funcs_.geturlnotify = &NPN_GetURLNotify; + host_funcs_.posturlnotify = &NPN_PostURLNotify; + host_funcs_.getvalue = &NPN_GetValue; + host_funcs_.setvalue = &NPN_SetValue; + host_funcs_.invalidaterect = &NPN_InvalidateRect; + host_funcs_.invalidateregion = &NPN_InvalidateRegion; + host_funcs_.forceredraw = &NPN_ForceRedraw; + + // These come from the Javascript Engine + host_funcs_.getstringidentifier = WebBindings::getStringIdentifier; + host_funcs_.getstringidentifiers = WebBindings::getStringIdentifiers; + host_funcs_.getintidentifier = WebBindings::getIntIdentifier; + host_funcs_.identifierisstring = WebBindings::identifierIsString; + host_funcs_.utf8fromidentifier = WebBindings::utf8FromIdentifier; + host_funcs_.intfromidentifier = WebBindings::intFromIdentifier; + host_funcs_.createobject = WebBindings::createObject; + host_funcs_.retainobject = WebBindings::retainObject; + host_funcs_.releaseobject = WebBindings::releaseObject; + host_funcs_.invoke = WebBindings::invoke; + host_funcs_.invokeDefault = WebBindings::invokeDefault; + host_funcs_.evaluate = WebBindings::evaluate; + host_funcs_.getproperty = WebBindings::getProperty; + host_funcs_.setproperty = WebBindings::setProperty; + host_funcs_.removeproperty = WebBindings::removeProperty; + host_funcs_.hasproperty = WebBindings::hasProperty; + host_funcs_.hasmethod = WebBindings::hasMethod; + host_funcs_.releasevariantvalue = WebBindings::releaseVariantValue; + host_funcs_.setexception = WebBindings::setException; + host_funcs_.pushpopupsenabledstate = NPN_PushPopupsEnabledState; + host_funcs_.poppopupsenabledstate = NPN_PopPopupsEnabledState; + host_funcs_.enumerate = WebBindings::enumerate; + host_funcs_.pluginthreadasynccall = NPN_PluginThreadAsyncCall; + host_funcs_.construct = WebBindings::construct; + host_funcs_.getvalueforurl = NPN_GetValueForURL; + host_funcs_.setvalueforurl = NPN_SetValueForURL; + host_funcs_.getauthenticationinfo = NPN_GetAuthenticationInfo; + host_funcs_.scheduletimer = NPN_ScheduleTimer; + host_funcs_.unscheduletimer = NPN_UnscheduleTimer; + host_funcs_.popupcontextmenu = NPN_PopUpContextMenu; + host_funcs_.convertpoint = NPN_ConvertPoint; +} + +void PluginHost::PatchNPNetscapeFuncs(NPNetscapeFuncs* overrides) { + // When running in the plugin process, we need to patch the NPN functions + // that the plugin calls to interact with NPObjects that we give. Otherwise + // the plugin will call the v8 NPN functions, which won't work since we have + // an NPObjectProxy and not a real v8 implementation. + if (overrides->invoke) + host_funcs_.invoke = overrides->invoke; + + if (overrides->invokeDefault) + host_funcs_.invokeDefault = overrides->invokeDefault; + + if (overrides->evaluate) + host_funcs_.evaluate = overrides->evaluate; + + if (overrides->getproperty) + host_funcs_.getproperty = overrides->getproperty; + + if (overrides->setproperty) + host_funcs_.setproperty = overrides->setproperty; + + if (overrides->removeproperty) + host_funcs_.removeproperty = overrides->removeproperty; + + if (overrides->hasproperty) + host_funcs_.hasproperty = overrides->hasproperty; + + if (overrides->hasmethod) + host_funcs_.hasmethod = overrides->hasmethod; + + if (overrides->setexception) + host_funcs_.setexception = overrides->setexception; + + if (overrides->enumerate) + host_funcs_.enumerate = overrides->enumerate; +} + +bool PluginHost::SetPostData(const char* buf, + uint32 length, + std::vector<std::string>* names, + std::vector<std::string>* values, + std::vector<char>* body) { + // Use a state table to do the parsing. Whitespace must be + // trimmed after the fact if desired. In our case, we actually + // don't care about the whitespace, because we're just going to + // pass this back into another POST. This function strips out the + // "Content-length" header and does not append it to the request. + + // + // This parser takes action only on state changes. + // + // Transition table: + // : \n NULL Other + // 0 GetHeader 1 2 4 0 + // 1 GetValue 1 0 3 1 + // 2 GetData 2 2 3 2 + // 3 DONE + // 4 ERR + // + enum { INPUT_COLON=0, INPUT_NEWLINE, INPUT_NULL, INPUT_OTHER }; + enum { GETNAME, GETVALUE, GETDATA, DONE, ERR }; + int statemachine[3][4] = { { GETVALUE, GETDATA, GETDATA, GETNAME }, + { GETVALUE, GETNAME, DONE, GETVALUE }, + { GETDATA, GETDATA, DONE, GETDATA } }; + std::string name, value; + const char* ptr = static_cast<const char*>(buf); + const char* start = ptr; + int state = GETNAME; // initial state + bool done = false; + bool err = false; + do { + int input; + + // Translate the current character into an input + // for the state table. + switch (*ptr) { + case ':' : + input = INPUT_COLON; + break; + case '\n': + input = INPUT_NEWLINE; + break; + case 0 : + input = INPUT_NULL; + break; + default : + input = INPUT_OTHER; + break; + } + + int newstate = statemachine[state][input]; + + // Take action based on the new state. + if (state != newstate) { + switch (newstate) { + case GETNAME: + // Got a value. + value = std::string(start, ptr - start); + TrimWhitespace(value, TRIM_ALL, &value); + // If the name field is empty, we'll skip this header + // but we won't error out. + if (!name.empty() && name != "content-length") { + names->push_back(name); + values->push_back(value); + } + start = ptr + 1; + break; + case GETVALUE: + // Got a header. + name = StringToLowerASCII(std::string(start, ptr - start)); + TrimWhitespace(name, TRIM_ALL, &name); + start = ptr + 1; + break; + case GETDATA: { + // Finished headers, now get body + if (*ptr) + start = ptr + 1; + size_t previous_size = body->size(); + size_t new_body_size = length - static_cast<int>(start - buf); + body->resize(previous_size + new_body_size); + if (!body->empty()) + memcpy(&body->front() + previous_size, start, new_body_size); + done = true; + break; + } + case ERR: + // error + err = true; + done = true; + break; + } + } + state = newstate; + ptr++; + } while (!done); + + return !err; +} + +} // namespace NPAPI + +extern "C" { + +// Allocates memory from the host's memory space. +void* NPN_MemAlloc(uint32_t size) { + scoped_refptr<NPAPI::PluginHost> host = NPAPI::PluginHost::Singleton(); + if (host != NULL) { + // Note: We must use the same allocator/deallocator + // that is used by the javascript library, as some of the + // JS APIs will pass memory to the plugin which the plugin + // will attempt to free. + return malloc(size); + } + return NULL; +} + +// Deallocates memory from the host's memory space +void NPN_MemFree(void* ptr) { + scoped_refptr<NPAPI::PluginHost> host = NPAPI::PluginHost::Singleton(); + if (host != NULL) { + if (ptr != NULL && ptr != reinterpret_cast<void*>(-1)) + free(ptr); + } +} + +// Requests that the host free a specified amount of memory. +uint32_t NPN_MemFlush(uint32_t size) { + // This is not relevant on Windows; MAC specific + return size; +} + +// This is for dynamic discovery of new plugins. +// Should force a re-scan of the plugins directory to load new ones. +void NPN_ReloadPlugins(NPBool reloadPages) { + // TODO: implement me + DLOG(INFO) << "NPN_ReloadPlugin is not implemented yet."; +} + +// Requests a range of bytes for a seekable stream. +NPError NPN_RequestRead(NPStream* stream, NPByteRange* range_list) { + if (!stream || !range_list) + return NPERR_GENERIC_ERROR; + + scoped_refptr<NPAPI::PluginInstance> plugin = + reinterpret_cast<NPAPI::PluginInstance*>(stream->ndata); + if (!plugin.get()) + return NPERR_GENERIC_ERROR; + + plugin->RequestRead(stream, range_list); + return NPERR_NO_ERROR; +} + +// Generic form of GetURL for common code between GetURL and GetURLNotify. +static NPError GetURLNotify(NPP id, + const char* url, + const char* target, + bool notify, + void* notify_data) { + if (!url) + return NPERR_INVALID_URL; + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin.get()) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + plugin->RequestURL(url, "GET", target, NULL, 0, notify, notify_data); + return NPERR_NO_ERROR; +} + +// Requests creation of a new stream with the contents of the +// specified URL; gets notification of the result. +NPError NPN_GetURLNotify(NPP id, + const char* url, + const char* target, + void* notify_data) { + // This is identical to NPN_GetURL, but after finishing, the + // browser will call NPP_URLNotify to inform the plugin that + // it has completed. + + // According to the NPAPI documentation, if target == _self + // or a parent to _self, the browser should return NPERR_INVALID_PARAM, + // because it can't notify the plugin once deleted. This is + // absolutely false; firefox doesn't do this, and Flash relies on + // being able to use this. + + // Also according to the NPAPI documentation, we should return + // NPERR_INVALID_URL if the url requested is not valid. However, + // this would require that we synchronously start fetching the + // URL. That just isn't practical. As such, there really is + // no way to return this error. From looking at the Firefox + // implementation, it doesn't look like Firefox does this either. + + return GetURLNotify(id, url, target, true, notify_data); +} + +NPError NPN_GetURL(NPP id, const char* url, const char* target) { + // Notes: + // Request from the Plugin to fetch content either for the plugin + // or to be placed into a browser window. + // + // If target == null, the browser fetches content and streams to plugin. + // otherwise, the browser loads content into an existing browser frame. + // If the target is the window/frame containing the plugin, the plugin + // may be destroyed. + // If the target is _blank, a mailto: or news: url open content in a new + // browser window + // If the target is _self, no other instance of the plugin is created. The + // plugin continues to operate in its own window + + return GetURLNotify(id, url, target, false, 0); +} + +// Generic form of PostURL for common code between PostURL and PostURLNotify. +static NPError PostURLNotify(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file, + bool notify, + void* notify_data) { + if (!url) + return NPERR_INVALID_URL; + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin.get()) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + std::string post_file_contents; + + if (file) { + // Post data to be uploaded from a file. This can be handled in two + // ways. + // 1. Read entire file and send the contents as if it was a post data + // specified in the argument + // 2. Send just the file details and read them in the browser at the + // time of sending the request. + // Approach 2 is more efficient but complicated. Approach 1 has a major + // drawback of sending potentially large data over two IPC hops. In a way + // 'large data over IPC' problem exists as it is in case of plugin giving + // the data directly instead of in a file. + // Currently we are going with the approach 1 to get the feature working. + // We can optimize this later with approach 2. + + // TODO(joshia): Design a scheme to send a file descriptor instead of + // entire file contents across. + + // Security alert: + // --------------- + // Here we are blindly uploading whatever file requested by a plugin. + // This is risky as someone could exploit a plugin to send private + // data in arbitrary locations. + // A malicious (non-sandboxed) plugin has unfeterred access to OS + // resources and can do this anyway without using browser's HTTP stack. + // FWIW, Firefox and Safari don't perform any security checks. + + if (!buf) + return NPERR_FILE_NOT_FOUND; + + std::string file_path_ascii(buf); + FilePath file_path; + static const char kFileUrlPrefix[] = "file:"; + if (StartsWithASCII(file_path_ascii, kFileUrlPrefix, false)) { + GURL file_url(file_path_ascii); + DCHECK(file_url.SchemeIsFile()); + net::FileURLToFilePath(file_url, &file_path); + } else { + file_path = FilePath::FromWStringHack( + base::SysNativeMBToWide(file_path_ascii)); + } + + file_util::FileInfo post_file_info = {0}; + if (!file_util::GetFileInfo(file_path, &post_file_info) || + post_file_info.is_directory) + return NPERR_FILE_NOT_FOUND; + + if (!file_util::ReadFileToString(file_path, &post_file_contents)) + return NPERR_FILE_NOT_FOUND; + + buf = post_file_contents.c_str(); + len = post_file_contents.size(); + } + + // The post data sent by a plugin contains both headers + // and post data. Example: + // Content-type: text/html + // Content-length: 200 + // + // <200 bytes of content here> + // + // Unfortunately, our stream needs these broken apart, + // so we need to parse the data and set headers and data + // separately. + plugin->RequestURL(url, "POST", target, buf, len, notify, notify_data); + return NPERR_NO_ERROR; +} + +NPError NPN_PostURLNotify(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file, + void* notify_data) { + return PostURLNotify(id, url, target, len, buf, file, true, notify_data); +} + +NPError NPN_PostURL(NPP id, + const char* url, + const char* target, + uint32_t len, + const char* buf, + NPBool file) { + // POSTs data to an URL, either from a temp file or a buffer. + // If file is true, buf contains a temp file (which host will delete after + // completing), and len contains the length of the filename. + // If file is false, buf contains the data to send, and len contains the + // length of the buffer + // + // If target is null, + // server response is returned to the plugin + // If target is _current, _self, or _top, + // server response is written to the plugin window and plugin is unloaded. + // If target is _new or _blank, + // server response is written to a new browser window + // If target is an existing frame, + // server response goes to that frame. + // + // For protocols other than FTP + // file uploads must be line-end converted from \r\n to \n + // + // Note: you cannot specify headers (even a blank line) in a memory buffer, + // use NPN_PostURLNotify + + return PostURLNotify(id, url, target, len, buf, file, false, 0); +} + +NPError NPN_NewStream(NPP id, + NPMIMEType type, + const char* target, + NPStream** stream) { + // Requests creation of a new data stream produced by the plugin, + // consumed by the browser. + // + // Browser should put this stream into a window target. + // + // TODO: implement me + DLOG(INFO) << "NPN_NewStream is not implemented yet."; + return NPERR_GENERIC_ERROR; +} + +int32_t NPN_Write(NPP id, NPStream* stream, int32_t len, void* buffer) { + // Writes data to an existing Plugin-created stream. + + // TODO: implement me + DLOG(INFO) << "NPN_Write is not implemented yet."; + return NPERR_GENERIC_ERROR; +} + +NPError NPN_DestroyStream(NPP id, NPStream* stream, NPReason reason) { + // Destroys a stream (could be created by plugin or browser). + // + // Reasons: + // NPRES_DONE - normal completion + // NPRES_USER_BREAK - user terminated + // NPRES_NETWORK_ERROR - network error (all errors fit here?) + // + // + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin.get() == NULL) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + return plugin->NPP_DestroyStream(stream, reason); +} + +const char* NPN_UserAgent(NPP id) { +#if defined(OS_WIN) + // Flash passes in a null id during the NP_initialize call. We need to + // default to the Mozilla user agent if we don't have an NPP instance or + // else Flash won't request windowless mode. + bool use_mozilla_user_agent = true; + if (id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin.get() && !plugin->use_mozilla_user_agent()) + use_mozilla_user_agent = false; + } + + if (use_mozilla_user_agent) + return "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9a1) " + "Gecko/20061103 Firefox/2.0a1"; +#elif defined(OS_MACOSX) + // Silverlight 4 doesn't handle events correctly unless we claim to be Safari. + scoped_refptr<NPAPI::PluginInstance> plugin; + if (id) + plugin = FindInstance(id); + if (plugin.get()) { + WebPluginInfo plugin_info = plugin->plugin_lib()->plugin_info(); + if (plugin_info.name == ASCIIToUTF16("Silverlight Plug-In") && + StartsWith(plugin_info.version, ASCIIToUTF16("4."), false)) { + return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-us) " + "AppleWebKit/534.1+ (KHTML, like Gecko) Version/5.0 Safari/533.16"; + } + } +#endif + + return webkit_glue::GetUserAgent(GURL()).c_str(); +} + +void NPN_Status(NPP id, const char* message) { + // Displays a message on the status line of the browser window. + + // TODO: implement me + DLOG(INFO) << "NPN_Status is not implemented yet."; +} + +void NPN_InvalidateRect(NPP id, NPRect *invalidRect) { + // Invalidates specified drawing area prior to repainting or refreshing a + // windowless plugin + + // Before a windowless plugin can refresh part of its drawing area, it must + // first invalidate it. This function causes the NPP_HandleEvent method to + // pass an update event or a paint message to the plug-in. After calling + // this method, the plug-in recieves a paint message asynchronously. + + // The browser redraws invalid areas of the document and any windowless + // plug-ins at regularly timed intervals. To force a paint message, the + // plug-in can call NPN_ForceRedraw after calling this method. + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + DCHECK(plugin.get() != NULL); + if (plugin.get() && plugin->webplugin()) { + if (invalidRect) { +#if defined(OS_WIN) + if (!plugin->windowless()) { + RECT rect = {0}; + rect.left = invalidRect->left; + rect.right = invalidRect->right; + rect.top = invalidRect->top; + rect.bottom = invalidRect->bottom; + ::InvalidateRect(plugin->window_handle(), &rect, false); + return; + } +#endif + gfx::Rect rect(invalidRect->left, + invalidRect->top, + invalidRect->right - invalidRect->left, + invalidRect->bottom - invalidRect->top); + plugin->webplugin()->InvalidateRect(rect); + } else { + plugin->webplugin()->Invalidate(); + } + } +} + +void NPN_InvalidateRegion(NPP id, NPRegion invalidRegion) { + // Invalidates a specified drawing region prior to repainting + // or refreshing a window-less plugin. + // + // Similar to NPN_InvalidateRect. + + // TODO: this is overkill--add platform-specific region handling (at the + // very least, fetch the region's bounding box and pass it to InvalidateRect). + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + DCHECK(plugin.get() != NULL); + if (plugin.get() && plugin->webplugin()) + plugin->webplugin()->Invalidate(); +} + +void NPN_ForceRedraw(NPP id) { + // Forces repaint for a windowless plug-in. + // + // We deliberately do not implement this; we don't want plugins forcing + // synchronous paints. +} + +NPError NPN_GetValue(NPP id, NPNVariable variable, void* value) { + // Allows the plugin to query the browser for information + // + // Variables: + // NPNVxDisplay (unix only) + // NPNVxtAppContext (unix only) + // NPNVnetscapeWindow (win only) - Gets the native window on which the + // plug-in drawing occurs, returns HWND + // NPNVjavascriptEnabledBool: tells whether Javascript is enabled + // NPNVasdEnabledBool: tells whether SmartUpdate is enabled + // NPNVOfflineBool: tells whether offline-mode is enabled + + NPError rv = NPERR_GENERIC_ERROR; + + switch (static_cast<int>(variable)) { + case NPNVWindowNPObject: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + NPObject *np_object = plugin->webplugin()->GetWindowScriptNPObject(); + // Return value is expected to be retained, as + // described here: + // <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess> + if (np_object) { + WebBindings::retainObject(np_object); + void **v = (void **)value; + *v = np_object; + rv = NPERR_NO_ERROR; + } else { + NOTREACHED(); + } + break; + } + case NPNVPluginElementNPObject: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + NPObject *np_object = plugin->webplugin()->GetPluginElement(); + // Return value is expected to be retained, as + // described here: + // <http://www.mozilla.org/projects/plugins/npruntime.html#browseraccess> + if (np_object) { + WebBindings::retainObject(np_object); + void** v = static_cast<void**>(value); + *v = np_object; + rv = NPERR_NO_ERROR; + } else { + NOTREACHED(); + } + break; + } + #if !defined(OS_MACOSX) // OS X doesn't have windowed plugins. + case NPNVnetscapeWindow: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin.get()) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + gfx::PluginWindowHandle handle = plugin->window_handle(); + *((void**)value) = (void*)handle; + rv = NPERR_NO_ERROR; + break; + } + #endif + case NPNVjavascriptEnabledBool: { + // yes, JS is enabled. + *((void**)value) = (void*)1; + rv = NPERR_NO_ERROR; + break; + } + #if defined(TOOLKIT_USES_GTK) + case NPNVToolkit: + // Tell them we are GTK2. (The alternative is GTK 1.2.) + *reinterpret_cast<int*>(value) = NPNVGtk2; + rv = NPERR_NO_ERROR; + break; + + case NPNVSupportsXEmbedBool: + *reinterpret_cast<NPBool*>(value) = true; + rv = NPERR_NO_ERROR; + break; + #endif + case NPNVSupportsWindowless: { + NPBool* supports_windowless = reinterpret_cast<NPBool*>(value); + *supports_windowless = true; + rv = NPERR_NO_ERROR; + break; + } + case NPNVprivateModeBool: { + NPBool* private_mode = reinterpret_cast<NPBool*>(value); + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + *private_mode = plugin->webplugin()->IsOffTheRecord(); + rv = NPERR_NO_ERROR; + break; + } + case default_plugin::kMissingPluginStatusStart + + default_plugin::MISSING_PLUGIN_AVAILABLE: + // fall through + case default_plugin::kMissingPluginStatusStart + + default_plugin::MISSING_PLUGIN_USER_STARTED_DOWNLOAD: { + // This is a hack for the default plugin to send notification to + // renderer. Even though we check if the plugin is the default plugin, + // we still need to worry about future standard change that may conflict + // with the variable definition, in order to avoid duplicate case clauses + // in this big switch statement. + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin->plugin_lib()->plugin_info().path.value() == + kDefaultPluginLibraryName) { + plugin->webplugin()->OnMissingPluginStatus( + variable - default_plugin::kMissingPluginStatusStart); + } + break; + } + #if defined(OS_MACOSX) + case NPNVpluginDrawingModel: { + // return the drawing model that was negotiated when we initialized. + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + *reinterpret_cast<int*>(value) = plugin->drawing_model(); + rv = NPERR_NO_ERROR; + break; + } +#ifndef NP_NO_QUICKDRAW + case NPNVsupportsQuickDrawBool: { + // We do not admit to supporting the QuickDraw drawing model. The logic + // here is that our QuickDraw plugin support is so rudimentary that we + // only want to use it as a fallback to keep plugins from crashing: if a + // plugin knows enough to ask, we want them to use CoreGraphics. + NPBool* supports_qd = reinterpret_cast<NPBool*>(value); + *supports_qd = false; + rv = NPERR_NO_ERROR; + break; + } +#endif + case NPNVsupportsCoreGraphicsBool: +#ifndef NP_NO_CARBON + case NPNVsupportsCarbonBool: +#endif + case NPNVsupportsCocoaBool: { + // we do support these drawing and event models. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = true; + rv = NPERR_NO_ERROR; + break; + } + case NPNVsupportsCoreAnimationBool: + case NPNVsupportsInvalidatingCoreAnimationBool: { + // We only support the Core Animation model on 10.6 and higher + // TODO(stuartmorgan): Once existing CA plugins have implemented the + // invalidating version, remove support for the other version. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = SupportsSharingAcceleratedSurfaces() ? true : false; + rv = NPERR_NO_ERROR; + break; + } + case NPNVsupportsOpenGLBool: { + // This drawing model was never widely supported, and we don't plan to + // support it. + NPBool* supports_model = reinterpret_cast<NPBool*>(value); + *supports_model = false; + rv = NPERR_NO_ERROR; + break; + } + #endif // OS_MACOSX + case NPNVPepperExtensions: + // Available for any plugin that attempts to get it. + // If the plugin is not started in a Pepper implementation, it + // will likely fail when it tries to use any of the functions + // attached to the extension vector. + rv = NPAPI::GetPepperExtensionsFunctions(value); + break; + default: + DLOG(INFO) << "NPN_GetValue(" << variable << ") is not implemented yet."; + break; + } + return rv; +} + +NPError NPN_SetValue(NPP id, NPPVariable variable, void* value) { + // Allows the plugin to set various modes + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + switch(variable) { + case NPPVpluginWindowBool: { + // Sets windowless mode for display of the plugin + // Note: the documentation at + // http://developer.mozilla.org/en/docs/NPN_SetValue is wrong. When + // value is NULL, the mode is set to true. This is the same way Mozilla + // works. + plugin->set_windowless(value == 0); + return NPERR_NO_ERROR; + } + case NPPVpluginTransparentBool: { + // Sets transparent mode for display of the plugin + // + // Transparent plugins require the browser to paint the background + // before having the plugin paint. By default, windowless plugins + // are transparent. Making a windowless plugin opaque means that + // the plugin does not require the browser to paint the background. + bool mode = (value != 0); + plugin->set_transparent(mode); + return NPERR_NO_ERROR; + } + case NPPVjavascriptPushCallerBool: + // Specifies whether you are pushing or popping the JSContext off. + // the stack + // TODO: implement me + DLOG(INFO) << + "NPN_SetValue(NPPVJavascriptPushCallerBool) is not implemented."; + return NPERR_GENERIC_ERROR; + case NPPVpluginKeepLibraryInMemory: + // Tells browser that plugin library should live longer than usual. + // TODO: implement me + DLOG(INFO) << + "NPN_SetValue(NPPVpluginKeepLibraryInMemory) is not implemented."; + return NPERR_GENERIC_ERROR; + #if defined(OS_MACOSX) + case NPPVpluginDrawingModel: { + int model = reinterpret_cast<int>(value); + if (model == NPDrawingModelCoreGraphics) { + plugin->set_drawing_model(static_cast<NPDrawingModel>(model)); + return NPERR_NO_ERROR; + } else if ((model == NPDrawingModelCoreAnimation || + model == NPDrawingModelInvalidatingCoreAnimation) && + SupportsSharingAcceleratedSurfaces()) { + plugin->set_drawing_model(static_cast<NPDrawingModel>(model)); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; + } + case NPPVpluginEventModel: { + // we support Carbon and Cocoa event models + int model = reinterpret_cast<int>(value); + switch (model) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: +#endif + case NPEventModelCocoa: + plugin->set_event_model(static_cast<NPEventModel>(model)); + return NPERR_NO_ERROR; + break; + } + return NPERR_GENERIC_ERROR; + } + #endif + default: + // TODO: implement me + DLOG(INFO) << "NPN_SetValue(" << variable << ") is not implemented."; + break; + } + + NOTREACHED(); + return NPERR_GENERIC_ERROR; +} + +void* NPN_GetJavaEnv() { + // TODO: implement me + DLOG(INFO) << "NPN_GetJavaEnv is not implemented."; + return NULL; +} + +void* NPN_GetJavaPeer(NPP) { + // TODO: implement me + DLOG(INFO) << "NPN_GetJavaPeer is not implemented."; + return NULL; +} + +void NPN_PushPopupsEnabledState(NPP id, NPBool enabled) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) + plugin->PushPopupsEnabledState(enabled ? true : false); +} + +void NPN_PopPopupsEnabledState(NPP id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) + plugin->PopPopupsEnabledState(); +} + +void NPN_PluginThreadAsyncCall(NPP id, + void (*func)(void*), + void* user_data) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) + plugin->PluginThreadAsyncCall(func, user_data); +} + +NPError NPN_GetValueForURL(NPP id, + NPNURLVariable variable, + const char* url, + char** value, + uint32_t* len) { + if (!id) + return NPERR_INVALID_PARAM; + + if (!url || !*url || !len) + return NPERR_INVALID_URL; + + *len = 0; + std::string result; + + switch (variable) { + case NPNURLVProxy: { + result = "DIRECT"; + if (!webkit_glue::FindProxyForUrl(GURL((std::string(url))), &result)) + return NPERR_GENERIC_ERROR; + + break; + } + case NPNURLVCookie: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NPERR_GENERIC_ERROR; + + webkit_glue::WebPlugin* webplugin = plugin->webplugin(); + if (!webplugin) + return NPERR_GENERIC_ERROR; + + // Bypass third-party cookie blocking by using the url as the + // first_party_for_cookies. + GURL cookies_url((std::string(url))); + result = webplugin->GetCookies(cookies_url, cookies_url); + break; + } + default: + return NPERR_GENERIC_ERROR; + } + + // Allocate this using the NPAPI allocator. The plugin will call + // NPN_Free to free this. + *value = static_cast<char*>(NPN_MemAlloc(result.length() + 1)); + base::strlcpy(*value, result.c_str(), result.length() + 1); + *len = result.length(); + + return NPERR_NO_ERROR; +} + +NPError NPN_SetValueForURL(NPP id, + NPNURLVariable variable, + const char* url, + const char* value, + uint32_t len) { + if (!id) + return NPERR_INVALID_PARAM; + + if (!url || !*url) + return NPERR_INVALID_URL; + + switch (variable) { + case NPNURLVCookie: { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return NPERR_GENERIC_ERROR; + + webkit_glue::WebPlugin* webplugin = plugin->webplugin(); + if (!webplugin) + return NPERR_GENERIC_ERROR; + + std::string cookie(value, len); + GURL cookies_url((std::string(url))); + webplugin->SetCookie(cookies_url, cookies_url, cookie); + return NPERR_NO_ERROR; + } + case NPNURLVProxy: + // We don't support setting proxy values, fall through... + break; + default: + // Fall through and return an error... + break; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPN_GetAuthenticationInfo(NPP id, + const char* protocol, + const char* host, + int32_t port, + const char* scheme, + const char* realm, + char** username, + uint32_t* ulen, + char** password, + uint32_t* plen) { + if (!id || !protocol || !host || !scheme || !realm || !username || + !ulen || !password || !plen) + return NPERR_INVALID_PARAM; + + // TODO: implement me (bug 23928) + return NPERR_GENERIC_ERROR; +} + +uint32_t NPN_ScheduleTimer(NPP id, + uint32_t interval, + NPBool repeat, + void (*func)(NPP id, uint32_t timer_id)) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (!plugin) + return 0; + + return plugin->ScheduleTimer(interval, repeat, func); +} + +void NPN_UnscheduleTimer(NPP id, uint32_t timer_id) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin) + plugin->UnscheduleTimer(timer_id); +} + +NPError NPN_PopUpContextMenu(NPP id, NPMenu* menu) { + if (!menu) + return NPERR_INVALID_PARAM; + + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin.get()) { + return plugin->PopUpContextMenu(menu); + } + NOTREACHED(); + return NPERR_GENERIC_ERROR; +} + +NPBool NPN_ConvertPoint(NPP id, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, + double *destX, double *destY, + NPCoordinateSpace destSpace) { + scoped_refptr<NPAPI::PluginInstance> plugin = FindInstance(id); + if (plugin.get()) { + return plugin->ConvertPoint(sourceX, sourceY, sourceSpace, + destX, destY, destSpace); + } + NOTREACHED(); + return false; +} + +} // extern "C" diff --git a/webkit/glue/plugins/plugin_host.h b/webkit/glue/plugins/plugin_host.h new file mode 100644 index 0000000..4763df1 --- /dev/null +++ b/webkit/glue/plugins/plugin_host.h @@ -0,0 +1,63 @@ +// Copyright (c) 2010 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. + +// TODO: Need mechanism to cleanup the static instance + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPI +{ +class PluginInstance; + +// The Plugin Host implements the NPN_xxx functions for NPAPI plugins. +// These are the functions exposed from the Plugin Host for use +// by the Plugin. +// +// The PluginHost is managed as a singleton. This isn't strictly +// necessary, but since the callback functions are all global C +// functions, there is really no point in having per-instance PluginHosts. +class PluginHost : public base::RefCounted<PluginHost> { + public: + // Access the single PluginHost instance. Callers + // must call deref() when finished with the object. + static PluginHost *Singleton(); + + // The table of functions provided to the plugin. + NPNetscapeFuncs *host_functions() { return &host_funcs_; } + + // Helper function for parsing post headers, and applying attributes + // to the stream. NPAPI post data include headers + data combined. + // This function parses it out and adds it to the stream in a WebKit + // style. + static bool SetPostData(const char *buf, + uint32 length, + std::vector<std::string>* names, + std::vector<std::string>* values, + std::vector<char>* body); + + void PatchNPNetscapeFuncs(NPNetscapeFuncs* overrides); + + private: + friend class base::RefCounted<PluginHost>; + + virtual ~PluginHost(); + + PluginHost(); + void InitializeHostFuncs(); + static scoped_refptr<PluginHost> singleton_; + NPNetscapeFuncs host_funcs_; + DISALLOW_COPY_AND_ASSIGN(PluginHost); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_HOST_H__ diff --git a/webkit/glue/plugins/plugin_instance.cc b/webkit/glue/plugins/plugin_instance.cc new file mode 100644 index 0000000..8506623 --- /dev/null +++ b/webkit/glue/plugins/plugin_instance.cc @@ -0,0 +1,632 @@ +// Copyright (c) 2006-2008 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 "build/build_config.h" + +#include "webkit/glue/plugins/plugin_instance.h" + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/plugin_string_stream.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "net/base/escape.h" + +#if defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#endif + +namespace NPAPI { + +PluginInstance::PluginInstance(PluginLib *plugin, const std::string &mime_type) + : plugin_(plugin), + npp_(0), + host_(PluginHost::Singleton()), + npp_functions_(plugin->functions()), + window_handle_(0), + windowless_(false), + transparent_(true), + webplugin_(0), + mime_type_(mime_type), + use_mozilla_user_agent_(false), +#if defined (OS_MACOSX) +#ifdef NP_NO_QUICKDRAW + drawing_model_(NPDrawingModelCoreGraphics), +#else + drawing_model_(NPDrawingModelQuickDraw), +#endif +#ifdef NP_NO_CARBON + event_model_(NPEventModelCocoa), +#else + event_model_(NPEventModelCarbon), +#endif + currently_handled_event_(NULL), +#endif + message_loop_(MessageLoop::current()), + load_manually_(false), + in_close_streams_(false), + next_timer_id_(1), + next_notify_id_(0), + next_range_request_id_(0) { + npp_ = new NPP_t(); + npp_->ndata = 0; + npp_->pdata = 0; + + memset(&zero_padding_, 0, sizeof(zero_padding_)); + DCHECK(message_loop_); +} + +PluginInstance::~PluginInstance() { + CloseStreams(); + + if (npp_ != 0) { + delete npp_; + npp_ = 0; + } + + if (plugin_) + plugin_->CloseInstance(); +} + +PluginStreamUrl* PluginInstance::CreateStream(unsigned long resource_id, + const GURL& url, + const std::string& mime_type, + int notify_id) { + + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + PluginStreamUrl* stream = new PluginStreamUrl( + resource_id, url, this, notify, notify_data); + + AddStream(stream); + return stream; +} + +void PluginInstance::AddStream(PluginStream* stream) { + open_streams_.push_back(stream); +} + +void PluginInstance::RemoveStream(PluginStream* stream) { + if (in_close_streams_) + return; + + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + if (*stream_index == stream) { + open_streams_.erase(stream_index); + break; + } + } +} + +bool PluginInstance::IsValidStream(const NPStream* stream) { + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + if ((*stream_index)->stream() == stream) + return true; + } + + return false; +} + +void PluginInstance::CloseStreams() { + in_close_streams_ = true; + for (unsigned int index = 0; index < open_streams_.size(); ++index) { + // Close all streams on the way down. + open_streams_[index]->Close(NPRES_USER_BREAK); + } + open_streams_.clear(); + in_close_streams_ = false; +} + +webkit_glue::WebPluginResourceClient* PluginInstance::GetRangeRequest( + int id) { + PendingRangeRequestMap::iterator iter = pending_range_requests_.find(id); + if (iter == pending_range_requests_.end()) { + NOTREACHED(); + return NULL; + } + + webkit_glue::WebPluginResourceClient* rv = iter->second->AsResourceClient(); + pending_range_requests_.erase(iter); + return rv; +} + +bool PluginInstance::Start(const GURL& url, + char** const param_names, + char** const param_values, + int param_count, + bool load_manually) { + load_manually_ = load_manually; + unsigned short mode = load_manually_ ? NP_FULL : NP_EMBED; + npp_->ndata = this; + + NPError err = NPP_New(mode, param_count, + const_cast<char **>(param_names), const_cast<char **>(param_values)); + return err == NPERR_NO_ERROR; +} + +NPObject *PluginInstance::GetPluginScriptableObject() { + NPObject *value = NULL; + NPError error = NPP_GetValue(NPPVpluginScriptableNPObject, &value); + if (error != NPERR_NO_ERROR || value == NULL) + return NULL; + return value; +} + +// WebPluginLoadDelegate methods +void PluginInstance::DidFinishLoadWithReason( + const GURL& url, NPReason reason, int notify_id) { + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + if (!notify) { + NOTREACHED(); + return; + } + + NPP_URLNotify(url.spec().c_str(), reason, notify_data); +} + +// NPAPI methods +NPError PluginInstance::NPP_New(unsigned short mode, + short argc, + char *argn[], + char *argv[]) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->newp != 0); + DCHECK(argc >= 0); + + if (npp_functions_->newp != 0) { + return npp_functions_->newp( + (NPMIMEType)mime_type_.c_str(), npp_, mode, argc, argn, argv, NULL); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +void PluginInstance::NPP_Destroy() { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->destroy != 0); + + if (npp_functions_->destroy != 0) { + NPSavedData *savedData = 0; + npp_functions_->destroy(npp_, &savedData); + + // TODO: Support savedData. Technically, these need to be + // saved on a per-URL basis, and then only passed + // to new instances of the plugin at the same URL. + // Sounds like a huge security risk. When we do support + // these, we should pass them back to the PluginLib + // to be stored there. + DCHECK(savedData == 0); + } + + for (unsigned int file_index = 0; file_index < files_created_.size(); + file_index++) { + file_util::Delete(files_created_[file_index], false); + } + + // Ensure that no timer callbacks are invoked after NPP_Destroy. + timers_.clear(); +} + +NPError PluginInstance::NPP_SetWindow(NPWindow *window) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->setwindow != 0); + + if (npp_functions_->setwindow != 0) { + return npp_functions_->setwindow(npp_, window); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_NewStream(NPMIMEType type, + NPStream *stream, + NPBool seekable, + unsigned short *stype) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->newstream != 0); + if (npp_functions_->newstream != 0) { + return npp_functions_->newstream(npp_, type, stream, seekable, stype); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_DestroyStream(NPStream *stream, NPReason reason) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->destroystream != 0); + + if (stream == NULL || !IsValidStream(stream) || (stream->ndata == NULL)) + return NPERR_INVALID_INSTANCE_ERROR; + + if (npp_functions_->destroystream != 0) { + NPError result = npp_functions_->destroystream(npp_, stream, reason); + stream->ndata = NULL; + return result; + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +int PluginInstance::NPP_WriteReady(NPStream *stream) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->writeready != 0); + if (npp_functions_->writeready != 0) { + return npp_functions_->writeready(npp_, stream); + } + return 0; +} + +int PluginInstance::NPP_Write(NPStream *stream, + int offset, + int len, + void *buffer) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->write != 0); + if (npp_functions_->write != 0) { + return npp_functions_->write(npp_, stream, offset, len, buffer); + } + return 0; +} + +void PluginInstance::NPP_StreamAsFile(NPStream *stream, const char *fname) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->asfile != 0); + if (npp_functions_->asfile != 0) { + npp_functions_->asfile(npp_, stream, fname); + } + + // Creating a temporary FilePath instance on the stack as the explicit + // FilePath constructor with StringType as an argument causes a compiler + // error when invoked via vector push back. + FilePath file_name = FilePath::FromWStringHack(UTF8ToWide(fname)); + files_created_.push_back(file_name); +} + +void PluginInstance::NPP_URLNotify(const char *url, + NPReason reason, + void *notifyData) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->urlnotify != 0); + if (npp_functions_->urlnotify != 0) { + npp_functions_->urlnotify(npp_, url, reason, notifyData); + } +} + +NPError PluginInstance::NPP_GetValue(NPPVariable variable, void *value) { + DCHECK(npp_functions_ != 0); + // getvalue is NULL for Shockwave + if (npp_functions_->getvalue != 0) { + return npp_functions_->getvalue(npp_, variable, value); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +NPError PluginInstance::NPP_SetValue(NPNVariable variable, void *value) { + DCHECK(npp_functions_ != 0); + if (npp_functions_->setvalue != 0) { + return npp_functions_->setvalue(npp_, variable, value); + } + return NPERR_INVALID_FUNCTABLE_ERROR; +} + +short PluginInstance::NPP_HandleEvent(void* event) { + DCHECK(npp_functions_ != 0); + DCHECK(npp_functions_->event != 0); + if (npp_functions_->event != 0) { + return npp_functions_->event(npp_, (void*)event); + } + return false; +} + +bool PluginInstance::NPP_Print(NPPrint* platform_print) { + DCHECK(npp_functions_ != 0); + if (npp_functions_->print != 0) { + npp_functions_->print(npp_, platform_print); + return true; + } + return false; +} + +void PluginInstance::SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + bool notify; + void* notify_data; + GetNotifyData(notify_id, ¬ify, ¬ify_data); + + if (success) { + PluginStringStream *stream = + new PluginStringStream(this, url, notify, notify_data); + AddStream(stream); + stream->SendToPlugin(result, "text/html"); + } else { + // NOTE: Sending an empty stream here will crash MacroMedia + // Flash 9. Just send the URL Notify. + if (notify) + NPP_URLNotify(url.spec().c_str(), NPRES_DONE, notify_data); + } +} + +void PluginInstance::DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified) { + DCHECK(load_manually_); + + plugin_data_stream_ = CreateStream(-1, url, mime_type, 0); + plugin_data_stream_->DidReceiveResponse(mime_type, headers, expected_length, + last_modified, true); +} + +void PluginInstance::DidReceiveManualData(const char* buffer, int length) { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidReceiveData(buffer, length, 0); + } +} + +void PluginInstance::DidFinishManualLoading() { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidFinishLoading(); + plugin_data_stream_->Close(NPRES_DONE); + plugin_data_stream_ = NULL; + } +} + +void PluginInstance::DidManualLoadFail() { + DCHECK(load_manually_); + if (plugin_data_stream_.get() != NULL) { + plugin_data_stream_->DidFail(); + plugin_data_stream_ = NULL; + } +} + +void PluginInstance::PluginThreadAsyncCall(void (*func)(void *), + void *user_data) { + message_loop_->PostTask( + FROM_HERE, NewRunnableMethod( + this, &PluginInstance::OnPluginThreadAsyncCall, func, user_data)); +} + +void PluginInstance::OnPluginThreadAsyncCall(void (*func)(void *), + void *user_data) { + // Do not invoke the callback if NPP_Destroy has already been invoked. + if (webplugin_) + func(user_data); +} + +uint32 PluginInstance::ScheduleTimer(uint32 interval, + NPBool repeat, + void (*func)(NPP id, uint32 timer_id)) { + // Use next timer id. + uint32 timer_id; + timer_id = next_timer_id_; + ++next_timer_id_; + DCHECK(next_timer_id_ != 0); + + // Record timer interval and repeat. + TimerInfo info; + info.interval = interval; + info.repeat = repeat ? true : false; + timers_[timer_id] = info; + + // Schedule the callback. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod( + this, &PluginInstance::OnTimerCall, func, npp_, timer_id), + interval); + return timer_id; +} + +void PluginInstance::UnscheduleTimer(uint32 timer_id) { + // Remove info about the timer. + TimerMap::iterator it = timers_.find(timer_id); + if (it != timers_.end()) + timers_.erase(it); +} + +#if !defined(OS_MACOSX) +NPError PluginInstance::PopUpContextMenu(NPMenu* menu) { + NOTIMPLEMENTED(); + return NPERR_GENERIC_ERROR; +} +#endif + +void PluginInstance::OnTimerCall(void (*func)(NPP id, uint32 timer_id), + NPP id, + uint32 timer_id) { + // Do not invoke callback if the timer has been unscheduled. + TimerMap::iterator it = timers_.find(timer_id); + if (it == timers_.end()) + return; + + // Get all information about the timer before invoking the callback. The + // callback might unschedule the timer. + TimerInfo info = it->second; + + func(id, timer_id); + + // If the timer was unscheduled by the callback, just free up the timer id. + if (timers_.find(timer_id) == timers_.end()) + return; + + // Reschedule repeating timers after invoking the callback so callback is not + // re-entered if it pumps the messager loop. + if (info.repeat) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + NewRunnableMethod( + this, &PluginInstance::OnTimerCall, func, npp_, timer_id), + info.interval); + } else { + timers_.erase(it); + } +} + +void PluginInstance::PushPopupsEnabledState(bool enabled) { + popups_enabled_stack_.push(enabled); +} + +void PluginInstance::PopPopupsEnabledState() { + popups_enabled_stack_.pop(); +} + +void PluginInstance::RequestRead(NPStream* stream, NPByteRange* range_list) { + std::string range_info = "bytes="; + + while (range_list) { + range_info += IntToString(range_list->offset); + range_info += "-"; + range_info += IntToString(range_list->offset + range_list->length - 1); + range_list = range_list->next; + if (range_list) { + range_info += ","; + } + } + + if (plugin_data_stream_) { + if (plugin_data_stream_->stream() == stream) { + webplugin_->CancelDocumentLoad(); + plugin_data_stream_ = NULL; + } + } + + // The lifetime of a NPStream instance depends on the PluginStream instance + // which owns it. When a plugin invokes NPN_RequestRead on a seekable stream, + // we don't want to create a new stream when the corresponding response is + // received. We send over a cookie which represents the PluginStream + // instance which is sent back from the renderer when the response is + // received. + std::vector<scoped_refptr<PluginStream> >::iterator stream_index; + for (stream_index = open_streams_.begin(); + stream_index != open_streams_.end(); ++stream_index) { + PluginStream* plugin_stream = *stream_index; + if (plugin_stream->stream() == stream) { + // A stream becomes seekable the first time NPN_RequestRead + // is called on it. + plugin_stream->set_seekable(true); + + pending_range_requests_[++next_range_request_id_] = plugin_stream; + webplugin_->InitiateHTTPRangeRequest( + stream->url, range_info.c_str(), next_range_request_id_); + return; + } + } + NOTREACHED(); +} + +void PluginInstance::RequestURL(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + bool notify, + void* notify_data) { + int notify_id = 0; + if (notify) { + notify_id = ++next_notify_id_; + pending_requests_[notify_id] = notify_data; + } + + webplugin_->HandleURLRequest( + url, method, target, buf, len, notify_id, popups_allowed()); +} + +bool PluginInstance::ConvertPoint(double source_x, double source_y, + NPCoordinateSpace source_space, + double* dest_x, double* dest_y, + NPCoordinateSpace dest_space) { +#if defined(OS_MACOSX) + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + + double flipped_screen_x = source_x; + double flipped_screen_y = source_y; + switch(source_space) { + case NPCoordinateSpacePlugin: + flipped_screen_x += plugin_origin_.x(); + flipped_screen_y += plugin_origin_.y(); + break; + case NPCoordinateSpaceWindow: + flipped_screen_x += containing_window_frame_.x(); + flipped_screen_y = containing_window_frame_.height() - source_y + + containing_window_frame_.y(); + break; + case NPCoordinateSpaceFlippedWindow: + flipped_screen_x += containing_window_frame_.x(); + flipped_screen_y += containing_window_frame_.y(); + break; + case NPCoordinateSpaceScreen: + flipped_screen_y = main_display_bounds.size.height - flipped_screen_y; + break; + case NPCoordinateSpaceFlippedScreen: + break; + default: + NOTREACHED(); + return false; + } + + double target_x = flipped_screen_x; + double target_y = flipped_screen_y; + switch(dest_space) { + case NPCoordinateSpacePlugin: + target_x -= plugin_origin_.x(); + target_y -= plugin_origin_.y(); + break; + case NPCoordinateSpaceWindow: + target_x -= containing_window_frame_.x(); + target_y -= containing_window_frame_.y(); + target_y = containing_window_frame_.height() - target_y; + break; + case NPCoordinateSpaceFlippedWindow: + target_x -= containing_window_frame_.x(); + target_y -= containing_window_frame_.y(); + break; + case NPCoordinateSpaceScreen: + target_y = main_display_bounds.size.height - flipped_screen_y; + break; + case NPCoordinateSpaceFlippedScreen: + break; + default: + NOTREACHED(); + return false; + } + + if (dest_x) + *dest_x = target_x; + if (dest_y) + *dest_y = target_y; + return true; +#else + NOTIMPLEMENTED(); + return false; +#endif +} + +void PluginInstance::GetNotifyData( + int notify_id, bool* notify, void** notify_data) { + PendingRequestMap::iterator iter = pending_requests_.find(notify_id); + if (iter != pending_requests_.end()) { + *notify = true; + *notify_data = iter->second; + pending_requests_.erase(iter); + } else { + *notify = false; + *notify_data = NULL; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_instance.h b/webkit/glue/plugins/plugin_instance.h new file mode 100644 index 0000000..36bf601 --- /dev/null +++ b/webkit/glue/plugins/plugin_instance.h @@ -0,0 +1,360 @@ +// Copyright (c) 2010 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. + +// TODO: Need to deal with NPAPI's NPSavedData. +// I haven't seen plugins use it yet. + +#ifndef WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ + +#include <map> +#include <set> +#include <stack> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "gfx/native_widget_types.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "googleurl/src/gurl.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +class MessageLoop; + +namespace webkit_glue { +class WebPlugin; +class WebPluginResourceClient; +} + +namespace NPAPI +{ +class PluginLib; +class PluginHost; +class PluginStream; +class PluginStreamUrl; +class PluginDataStream; +#if defined(OS_MACOSX) +class ScopedCurrentPluginEvent; +#endif + +// A PluginInstance is an active, running instance of a Plugin. +// A single plugin may have many PluginInstances. +class PluginInstance : public base::RefCountedThreadSafe<PluginInstance> { + public: + // Create a new instance of a plugin. The PluginInstance + // will hold a reference to the plugin. + PluginInstance(PluginLib *plugin, const std::string &mime_type); + + // Activates the instance by calling NPP_New. + // This should be called after our instance is all + // setup from the host side and we are ready to receive + // requests from the plugin. We must not call any + // functions on the plugin instance until start has + // been called. + // + // url: The instance URL. + // param_names: the list of names of attributes passed via the + // element. + // param_values: the list of values corresponding to param_names + // param_count: number of attributes + // load_manually: if true indicates that the plugin data would be passed + // from webkit. if false indicates that the plugin should + // download the data. + // This also controls whether the plugin is instantiated as + // a full page plugin (NP_FULL) or embedded (NP_EMBED) + // + bool Start(const GURL& url, + char** const param_names, + char** const param_values, + int param_count, + bool load_manually); + + // NPAPI's instance identifier for this instance + NPP npp() { return npp_; } + + // Get/Set for the instance's window handle. + gfx::PluginWindowHandle window_handle() const { return window_handle_; } + void set_window_handle(gfx::PluginWindowHandle value) { + window_handle_ = value; + } + + // Get/Set whether this instance is in Windowless mode. + // Default is false. + bool windowless() { return windowless_; } + void set_windowless(bool value) { windowless_ = value; } + + // Get/Set whether this instance is transparent. + // This only applies to windowless plugins. Transparent + // plugins require that webkit paint the background. + // Default is true. + bool transparent() { return transparent_; } + void set_transparent(bool value) { transparent_ = value; } + + // Get/Set the WebPlugin associated with this instance + webkit_glue::WebPlugin* webplugin() { return webplugin_; } + void set_web_plugin(webkit_glue::WebPlugin* webplugin) { + webplugin_ = webplugin; + } + + // Get the mimeType for this plugin stream + const std::string &mime_type() { return mime_type_; } + + NPAPI::PluginLib* plugin_lib() { return plugin_; } + +#if defined(OS_MACOSX) + // Get/Set the Mac NPAPI drawing and event models + NPDrawingModel drawing_model() { return drawing_model_; } + void set_drawing_model(NPDrawingModel value) { drawing_model_ = value; } + NPEventModel event_model() { return event_model_; } + void set_event_model(NPEventModel value) { event_model_ = value; } + // Updates the instance's tracking of the location of the plugin location + // relative to the upper left of the screen. + void set_plugin_origin(const gfx::Point& origin) { plugin_origin_ = origin; } + // Updates the instance's tracking of the frame of the containing window + // relative to the upper left of the screen. + void set_window_frame(const gfx::Rect& frame) { + containing_window_frame_ = frame; + } +#endif + + // Creates a stream for sending an URL. If notify_id is non-zero, it will + // send a notification to the plugin when the stream is complete; otherwise it + // will not. Set object_url to true if the load is for the object tag's url, + // or false if it's for a url that the plugin fetched through + // NPN_GetUrl[Notify]. + PluginStreamUrl* CreateStream(unsigned long resource_id, + const GURL& url, + const std::string& mime_type, + int notify_id); + + // For each instance, we track all streams. When the + // instance closes, all remaining streams are also + // closed. All streams associated with this instance + // should call AddStream so that they can be cleaned + // up when the instance shuts down. + void AddStream(PluginStream* stream); + + // This is called when a stream is closed. We remove the stream from the + // list, which releases the reference maintained to the stream. + void RemoveStream(PluginStream* stream); + + // Closes all open streams on this instance. + void CloseStreams(); + + // Returns the WebPluginResourceClient object for a stream that has become + // seekable. + webkit_glue::WebPluginResourceClient* GetRangeRequest(int id); + + // Have the plugin create it's script object. + NPObject *GetPluginScriptableObject(); + + // WebViewDelegate methods that we implement. This is for handling + // callbacks during getURLNotify. + void DidFinishLoadWithReason(const GURL& url, NPReason reason, int notify_id); + + // If true, send the Mozilla user agent instead of Chrome's to the plugin. + bool use_mozilla_user_agent() { return use_mozilla_user_agent_; } + void set_use_mozilla_user_agent() { use_mozilla_user_agent_ = true; } + + // Helper that implements NPN_PluginThreadAsyncCall semantics + void PluginThreadAsyncCall(void (*func)(void *), + void *userData); + + uint32 ScheduleTimer(uint32 interval, + NPBool repeat, + void (*func)(NPP id, uint32 timer_id)); + + void UnscheduleTimer(uint32 timer_id); + + bool ConvertPoint(double source_x, double source_y, + NPCoordinateSpace source_space, + double* dest_x, double* dest_y, + NPCoordinateSpace dest_space); + + NPError PopUpContextMenu(NPMenu* menu); + + // + // NPAPI methods for calling the Plugin Instance + // + NPError NPP_New(unsigned short, short, char *[], char *[]); + NPError NPP_SetWindow(NPWindow *); + NPError NPP_NewStream(NPMIMEType, NPStream *, NPBool, unsigned short *); + NPError NPP_DestroyStream(NPStream *, NPReason); + int NPP_WriteReady(NPStream *); + int NPP_Write(NPStream *, int, int, void *); + void NPP_StreamAsFile(NPStream *, const char *); + void NPP_URLNotify(const char *, NPReason, void *); + NPError NPP_GetValue(NPPVariable, void *); + NPError NPP_SetValue(NPNVariable, void *); + short NPP_HandleEvent(void*); + void NPP_Destroy(); + bool NPP_Print(NPPrint* platform_print); + + void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + + void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified); + void DidReceiveManualData(const char* buffer, int length); + void DidFinishManualLoading(); + void DidManualLoadFail(); + + void PushPopupsEnabledState(bool enabled); + void PopPopupsEnabledState(); + + bool popups_allowed() const { + return popups_enabled_stack_.empty() ? false : popups_enabled_stack_.top(); + } + + // Initiates byte range reads for plugins. + void RequestRead(NPStream* stream, NPByteRange* range_list); + + // Handles GetURL/GetURLNotify/PostURL/PostURLNotify requests initiated + // by plugins. + void RequestURL(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + bool notify, + void* notify_data); + + private: + friend class base::RefCountedThreadSafe<PluginInstance>; + +#if defined(OS_MACOSX) + friend class ScopedCurrentPluginEvent; + // Sets the event that the plugin is currently handling. The object is not + // owned or copied, so the caller must call this again with NULL before the + // event pointer becomes invalid. Clients use ScopedCurrentPluginEvent rather + // than calling this directly. + void set_currently_handled_event(NPCocoaEvent* event) { + currently_handled_event_ = event; + } +#endif + + ~PluginInstance(); + void OnPluginThreadAsyncCall(void (*func)(void *), void *userData); + void OnTimerCall(void (*func)(NPP id, uint32 timer_id), + NPP id, uint32 timer_id); + bool IsValidStream(const NPStream* stream); + void GetNotifyData(int notify_id, bool* notify, void** notify_data); + + // This is a hack to get the real player plugin to work with chrome + // The real player plugin dll(nppl3260) when loaded by firefox is loaded via + // the NS COM API which is analogous to win32 COM. So the NPAPI functions in + // the plugin are invoked via an interface by firefox. The plugin instance + // handle which is passed to every NPAPI method is owned by the real player + // plugin, i.e. it expects the ndata member to point to a structure which + // it knows about. Eventually it dereferences this structure and compares + // a member variable at offset 0x24(Version 6.0.11.2888) /2D (Version + // 6.0.11.3088) with 0 and on failing this check, takes a different code + // path which causes a crash. Safari and Opera work with version 6.0.11.2888 + // by chance as their ndata structure contains a 0 at the location which real + // player checks:(. They crash with version 6.0.11.3088 as well. The + // following member just adds a 96 byte padding to our PluginInstance class + // which is passed in the ndata member. This magic number works correctly on + // Vista with UAC on or off :(. + // NOTE: Please dont change the ordering of the member variables + // New members should be added after this padding array. + // TODO(iyengar) : Disassemble the Realplayer ndata structure and look into + // the possiblity of conforming to it (http://b/issue?id=936667). We + // could also log a bug with Real, which would save the effort. + uint8 zero_padding_[96]; + scoped_refptr<NPAPI::PluginLib> plugin_; + NPP npp_; + scoped_refptr<PluginHost> host_; + NPPluginFuncs* npp_functions_; + std::vector<scoped_refptr<PluginStream> > open_streams_; + gfx::PluginWindowHandle window_handle_; + bool windowless_; + bool transparent_; + webkit_glue::WebPlugin* webplugin_; + std::string mime_type_; + GURL get_url_; + intptr_t get_notify_data_; + bool use_mozilla_user_agent_; +#if defined(OS_MACOSX) + NPDrawingModel drawing_model_; + NPEventModel event_model_; + gfx::Point plugin_origin_; + gfx::Rect containing_window_frame_; + NPCocoaEvent* currently_handled_event_; // weak +#endif + MessageLoop* message_loop_; + scoped_refptr<PluginStreamUrl> plugin_data_stream_; + + // This flag if true indicates that the plugin data would be passed from + // webkit. if false indicates that the plugin should download the data. + bool load_manually_; + + // Stack indicating if popups are to be enabled for the outgoing + // NPN_GetURL/NPN_GetURLNotify calls. + std::stack<bool> popups_enabled_stack_; + + // True if in CloseStreams(). + bool in_close_streams_; + + // List of files created for the current plugin instance. File names are + // added to the list every time the NPP_StreamAsFile function is called. + std::vector<FilePath> files_created_; + + // Next unusued timer id. + uint32 next_timer_id_; + + // Map of timer id to settings for timer. + struct TimerInfo { + uint32 interval; + bool repeat; + }; + typedef std::map<uint32, TimerInfo> TimerMap; + TimerMap timers_; + + // Tracks pending GET/POST requests so that the plugin-given data doesn't + // cross process boundaries to an untrusted process. + typedef std::map<int, void*> PendingRequestMap; + PendingRequestMap pending_requests_; + int next_notify_id_; + + // Used to track pending range requests so that when WebPlugin replies to us + // we can match the reply to the stream. + typedef std::map<int, scoped_refptr<PluginStream> > PendingRangeRequestMap; + PendingRangeRequestMap pending_range_requests_; + int next_range_request_id_; + + DISALLOW_COPY_AND_ASSIGN(PluginInstance); +}; + +#if defined(OS_MACOSX) +// Helper to simplify correct usage of set_currently_handled_event. +// Instantiating will set |instance|'s currently handled to |event| for the +// lifetime of the object, then NULL when it goes out of scope. +class ScopedCurrentPluginEvent { + public: + ScopedCurrentPluginEvent(PluginInstance* instance, NPCocoaEvent* event) + : instance_(instance) { + instance_->set_currently_handled_event(event); + } + ~ScopedCurrentPluginEvent() { + instance_->set_currently_handled_event(NULL); + } + private: + scoped_refptr<PluginInstance> instance_; + DISALLOW_COPY_AND_ASSIGN(ScopedCurrentPluginEvent); +}; +#endif + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_INSTANCE_H__ diff --git a/webkit/glue/plugins/plugin_instance_mac.mm b/webkit/glue/plugins/plugin_instance_mac.mm new file mode 100644 index 0000000..9800198 --- /dev/null +++ b/webkit/glue/plugins/plugin_instance_mac.mm @@ -0,0 +1,133 @@ +// Copyright (c) 2010 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 "build/build_config.h" + +#import <AppKit/AppKit.h> + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +// When C++ exceptions are disabled, the C++ library defines |try| and +// |catch| so as to allow exception-expecting C++ code to build properly when +// language support for exceptions is not present. These macros interfere +// with the use of |@try| and |@catch| in Objective-C files such as this one. +// Undefine these macros here, after everything has been #included, since +// there will be no C++ uses and only Objective-C uses from this point on. +#undef try +#undef catch + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@interface NSMenu (SnowLeopardMenuPopUpDeclaration) +- (BOOL)popUpMenuPositioningItem:(NSMenuItem*)item + atLocation:(NSPoint)location + inView:(NSView*)view; +@end +#endif + +namespace { + +// Returns an autoreleased NSEvent constructed from the given np_event, +// targeting the given window. +NSEvent* NSEventForNPCocoaEvent(NPCocoaEvent* np_event, NSWindow* window) { + bool mouse_down = 1; + switch (np_event->type) { + case NPCocoaEventMouseDown: + mouse_down = 1; + break; + case NPCocoaEventMouseUp: + mouse_down = 0; + break; + default: + // If plugins start bringing up context menus for things other than + // clicks, this will need more plumbing; for now just log it and proceed + // as if it were a mouse down. + NOTREACHED(); + } + NSEventType event_type = NSLeftMouseDown; + switch (np_event->data.mouse.buttonNumber) { + case 0: + event_type = mouse_down ? NSLeftMouseDown : NSLeftMouseUp; + break; + case 1: + event_type = mouse_down ? NSRightMouseDown : NSRightMouseUp; + break; + default: + event_type = mouse_down ? NSOtherMouseDown : NSOtherMouseUp; + break; + } + + NSInteger click_count = np_event->data.mouse.clickCount; + NSInteger modifiers = np_event->data.mouse.modifierFlags; + // NPCocoaEvent doesn't have a timestamp, so just use the current time. + NSEvent* event = + [NSEvent mouseEventWithType:event_type + location:NSMakePoint(0, 0) + modifierFlags:modifiers + timestamp:[[NSApp currentEvent] timestamp] + windowNumber:[window windowNumber] + context:[NSGraphicsContext currentContext] + eventNumber:0 + clickCount:click_count + pressure:1.0]; + return event; +} + +} // namespace + +namespace NPAPI { + +NPError PluginInstance::PopUpContextMenu(NPMenu* menu) { + if (!currently_handled_event_) + return NPERR_GENERIC_ERROR; + + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + NSPoint screen_point = NSMakePoint( + plugin_origin_.x() + currently_handled_event_->data.mouse.pluginX, + plugin_origin_.y() + currently_handled_event_->data.mouse.pluginY); + // Plugin offsets are upper-left based, so flip vertically for Cocoa. + screen_point.y = main_display_bounds.size.height - screen_point.y; + + NSMenu* nsmenu = reinterpret_cast<NSMenu*>(menu); + NPError return_val = NPERR_NO_ERROR; + NSWindow* window = nil; + @try { + if ([nsmenu respondsToSelector: + @selector(popUpMenuPositioningItem:atLocation:inView:)]) { + [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil]; + } else { + NSRect dummy_window_rect = NSMakeRect(screen_point.x, screen_point.y, + 1, 1); + window = [[NSWindow alloc] initWithContentRect:dummy_window_rect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreNonretained + defer:YES]; + [window setTitle:@"PopupMenuDummy"]; // Lets interposing identify it. + [window setAlphaValue:0]; + [window makeKeyAndOrderFront:nil]; + [NSMenu popUpContextMenu:nsmenu + withEvent:NSEventForNPCocoaEvent(currently_handled_event_, + window) + forView:[window contentView]]; + } + } + @catch (NSException* e) { + NSLog(@"Caught exception while handling PopUpContextMenu: %@", e); + return_val = NPERR_GENERIC_ERROR; + } + + if (window) { + @try { + [window orderOut:nil]; + [window release]; + } + @catch (NSException* e) { + NSLog(@"Caught exception while cleaning up in PopUpContextMenu: %@", e); + } + } + + return return_val; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib.cc b/webkit/glue/plugins/plugin_lib.cc new file mode 100644 index 0000000..877548e --- /dev/null +++ b/webkit/glue/plugins/plugin_lib.cc @@ -0,0 +1,315 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_lib.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_list.h" + +namespace NPAPI { + +const char kPluginLibrariesLoadedCounter[] = "PluginLibrariesLoaded"; +const char kPluginInstancesActiveCounter[] = "PluginInstancesActive"; + +// A list of all the instantiated plugins. +static std::vector<scoped_refptr<PluginLib> >* g_loaded_libs; + +PluginLib* PluginLib::CreatePluginLib(const FilePath& filename) { + // We can only have one PluginLib object per plugin as it controls the per + // instance function calls (i.e. NP_Initialize and NP_Shutdown). So we keep + // a map of PluginLib objects. + if (!g_loaded_libs) + g_loaded_libs = new std::vector<scoped_refptr<PluginLib> >; + + for (size_t i = 0; i < g_loaded_libs->size(); ++i) { + if ((*g_loaded_libs)[i]->plugin_info().path == filename) + return (*g_loaded_libs)[i]; + } + + WebPluginInfo info; + const PluginEntryPoints* entry_points = NULL; + if (!PluginList::Singleton()->ReadPluginInfo(filename, &info, &entry_points)) + return NULL; + + return new PluginLib(info, entry_points); +} + +void PluginLib::UnloadAllPlugins() { + if (g_loaded_libs) { + // PluginLib::Unload() can remove items from the list and even delete + // the list when it removes the last item, so we must work with a copy + // of the list so that we don't get the carpet removed under our feet. + std::vector<scoped_refptr<PluginLib> > loaded_libs(*g_loaded_libs); + for (size_t i = 0; i < loaded_libs.size(); ++i) + loaded_libs[i]->Unload(); + + if (g_loaded_libs && g_loaded_libs->empty()) { + delete g_loaded_libs; + g_loaded_libs = NULL; + } + } +} + +void PluginLib::ShutdownAllPlugins() { + if (g_loaded_libs) { + for (size_t i = 0; i < g_loaded_libs->size(); ++i) + (*g_loaded_libs)[i]->Shutdown(); + } +} + +PluginLib::PluginLib(const WebPluginInfo& info, + const PluginEntryPoints* entry_points) + : web_plugin_info_(info), + library_(NULL), + initialized_(false), + saved_data_(0), + instance_count_(0), + skip_unload_(false) { + StatsCounter(kPluginLibrariesLoadedCounter).Increment(); + memset(static_cast<void*>(&plugin_funcs_), 0, sizeof(plugin_funcs_)); + g_loaded_libs->push_back(this); + + if (entry_points) { + internal_ = true; + entry_points_ = *entry_points; + } else { + internal_ = false; + // We will read the entry points from the plugin directly. + memset(&entry_points_, 0, sizeof(entry_points_)); + } +} + +PluginLib::~PluginLib() { + StatsCounter(kPluginLibrariesLoadedCounter).Decrement(); + if (saved_data_ != 0) { + // TODO - delete the savedData object here + } +} + +NPPluginFuncs* PluginLib::functions() { + return &plugin_funcs_; +} + +NPError PluginLib::NP_Initialize() { + LOG(INFO) << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() << + "): initialized=" << initialized_; + if (initialized_) + return NPERR_NO_ERROR; + + if (!Load()) + return NPERR_MODULE_LOAD_FAILED_ERROR; + + PluginHost* host = PluginHost::Singleton(); + if (host == 0) + return NPERR_GENERIC_ERROR; + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + NPError rv = entry_points_.np_initialize(host->host_functions(), + &plugin_funcs_); +#else + NPError rv = entry_points_.np_initialize(host->host_functions()); +#if defined(OS_MACOSX) + // On the Mac, we need to get entry points after calling np_initialize to + // match the behavior of other browsers. + if (rv == NPERR_NO_ERROR) { + rv = entry_points_.np_getentrypoints(&plugin_funcs_); + } +#endif // OS_MACOSX +#endif + LOG(INFO) << "PluginLib::NP_Initialize(" << web_plugin_info_.path.value() << + "): result=" << rv; + initialized_ = (rv == NPERR_NO_ERROR); + return rv; +} + +void PluginLib::NP_Shutdown(void) { + DCHECK(initialized_); + entry_points_.np_shutdown(); +} + +void PluginLib::PreventLibraryUnload() { + skip_unload_ = true; +} + +PluginInstance* PluginLib::CreateInstance(const std::string& mime_type) { + PluginInstance* new_instance = new PluginInstance(this, mime_type); + instance_count_++; + StatsCounter(kPluginInstancesActiveCounter).Increment(); + DCHECK_NE(static_cast<PluginInstance*>(NULL), new_instance); + return new_instance; +} + +void PluginLib::CloseInstance() { + StatsCounter(kPluginInstancesActiveCounter).Decrement(); + instance_count_--; + // If a plugin is running in its own process it will get unloaded on process + // shutdown. + if ((instance_count_ == 0) && webkit_glue::IsPluginRunningInRendererProcess()) + Unload(); +} + +bool PluginLib::Load() { + if (library_) + return true; + + bool rv = false; + base::NativeLibrary library = 0; + + if (!internal_) { + library = base::LoadNativeLibrary(web_plugin_info_.path); + if (library == 0) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Couldn't load plugin " << web_plugin_info_.path.value(); + return rv; + } + +#if defined(OS_MACOSX) + // According to the WebKit source, QuickTime at least requires us to call + // UseResFile on the plugin resources before loading. + if (library->bundle_resource_ref != -1) + UseResFile(library->bundle_resource_ref); +#endif + + rv = true; // assume success now + + entry_points_.np_initialize = + (NP_InitializeFunc)base::GetFunctionPointerFromNativeLibrary(library, + "NP_Initialize"); + if (entry_points_.np_initialize == 0) + rv = false; + +#if defined(OS_WIN) || defined(OS_MACOSX) + entry_points_.np_getentrypoints = + (NP_GetEntryPointsFunc)base::GetFunctionPointerFromNativeLibrary( + library, "NP_GetEntryPoints"); + if (entry_points_.np_getentrypoints == 0) + rv = false; +#endif + + entry_points_.np_shutdown = + (NP_ShutdownFunc)base::GetFunctionPointerFromNativeLibrary(library, + "NP_Shutdown"); + if (entry_points_.np_shutdown == 0) + rv = false; + } else { + rv = true; + } + + if (rv) { + plugin_funcs_.size = sizeof(plugin_funcs_); + plugin_funcs_.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; +#if !defined(OS_POSIX) + if (entry_points_.np_getentrypoints(&plugin_funcs_) != NPERR_NO_ERROR) + rv = false; +#else + // On Linux and Mac, we get the plugin entry points during NP_Initialize. +#endif + } + + if (!internal_) { + if (rv) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Plugin " << web_plugin_info_.path.value() + << " loaded successfully."; + library_ = library; + } else { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Plugin " << web_plugin_info_.path.value() + << " failed to load, unloading."; + base::UnloadNativeLibrary(library); + } + } + + return rv; +} + +// This class implements delayed NP_Shutdown and FreeLibrary on the plugin dll. +class FreePluginLibraryTask : public Task { + public: + FreePluginLibraryTask(base::NativeLibrary library, + NP_ShutdownFunc shutdown_func) + : library_(library), + NP_Shutdown_(shutdown_func) { + } + + ~FreePluginLibraryTask() {} + + void Run() { + if (NP_Shutdown_) + NP_Shutdown_(); + + if (library_) { + base::UnloadNativeLibrary(library_); + library_ = NULL; + } + } + + private: + base::NativeLibrary library_; + NP_ShutdownFunc NP_Shutdown_; + DISALLOW_COPY_AND_ASSIGN(FreePluginLibraryTask); +}; + +void PluginLib::Unload() { + if (!internal_ && library_) { + // In case of single process mode, a plugin can delete itself + // by executing a script. So delay the unloading of the library + // so that the plugin will have a chance to unwind. + bool defer_unload = webkit_glue::IsPluginRunningInRendererProcess(); + +/* TODO(dglazkov): Revisit when re-enabling the JSC build. +#if USE(JSC) + // The plugin NPAPI instances may still be around. Delay the + // NP_Shutdown and FreeLibrary calls at least till the next + // peek message. + defer_unload = true; +#endif +*/ + + if (defer_unload) { + FreePluginLibraryTask* free_library_task = + new FreePluginLibraryTask(skip_unload_ ? NULL : library_, + entry_points_.np_shutdown); + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Scheduling delayed unload for plugin " + << web_plugin_info_.path.value(); + MessageLoop::current()->PostTask(FROM_HERE, free_library_task); + } else { + Shutdown(); + if (!skip_unload_) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Unloading plugin " << web_plugin_info_.path.value(); + base::UnloadNativeLibrary(library_); + } + } + + library_ = NULL; + } + + for (size_t i = 0; i < g_loaded_libs->size(); ++i) { + if ((*g_loaded_libs)[i].get() == this) { + g_loaded_libs->erase(g_loaded_libs->begin() + i); + break; + } + } + if (g_loaded_libs->empty()) { + delete g_loaded_libs; + g_loaded_libs = NULL; + } +} + +void PluginLib::Shutdown() { + if (initialized_ && !internal_) { + NP_Shutdown(); + initialized_ = false; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib.h b/webkit/glue/plugins/plugin_lib.h new file mode 100644 index 0000000..647916e --- /dev/null +++ b/webkit/glue/plugins/plugin_lib.h @@ -0,0 +1,120 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/native_library.h" +#include "base/ref_counted.h" +#include "build/build_config.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/webplugin.h" + +struct WebPluginInfo; + +namespace NPAPI { + +class PluginInstance; + +// A PluginLib is a single NPAPI Plugin Library, and is the lifecycle +// manager for new PluginInstances. +class PluginLib : public base::RefCounted<PluginLib> { + public: + static PluginLib* CreatePluginLib(const FilePath& filename); + + // Creates a WebPluginInfo structure given a plugin's path. On success + // returns true, with the information being put into "info". + // Returns false if the library couldn't be found, or if it's not a plugin. + static bool ReadWebPluginInfo(const FilePath& filename, WebPluginInfo* info); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Parse the result of an NP_GetMIMEDescription() call. + // This API is only used on Unixes, and is exposed here for testing. + static void ParseMIMEDescription(const std::string& description, + std::vector<WebPluginMimeType>* mime_types); +#endif + + // Unloads all the loaded plugin libraries and cleans up the plugin map. + static void UnloadAllPlugins(); + + // Shuts down all loaded plugin instances. + static void ShutdownAllPlugins(); + + // Get the Plugin's function pointer table. + NPPluginFuncs* functions(); + + // Creates a new instance of this plugin. + PluginInstance* CreateInstance(const std::string& mime_type); + + // Called by the instance when the instance is tearing down. + void CloseInstance(); + + // Gets information about this plugin and the mime types that it + // supports. + const WebPluginInfo& plugin_info() { return web_plugin_info_; } + + bool internal() { return internal_; } + + // + // NPAPI functions + // + + // NPAPI method to initialize a Plugin. + // Initialize can be safely called multiple times + NPError NP_Initialize(); + + // NPAPI method to shutdown a Plugin. + void NP_Shutdown(void); + + int instance_count() const { return instance_count_; } + + // Prevents the library code from being unload when Unload() is called (since + // some plugins crash if unloaded). + void PreventLibraryUnload(); + + // protected for testability. + protected: + friend class base::RefCounted<PluginLib>; + + // Creates a new PluginLib. + // |entry_points| is non-NULL for internal plugins. + PluginLib(const WebPluginInfo& info, + const PluginEntryPoints* entry_points); + + virtual ~PluginLib(); + + // Attempts to load the plugin from the library. + // Returns true if it is a legitimate plugin, false otherwise + bool Load(); + + // Unloads the plugin library. + void Unload(); + + // Shutdown the plugin library. + void Shutdown(); + + private: + bool internal_; // True for plugins that are built-in into chrome binaries. + WebPluginInfo web_plugin_info_; // Supported mime types, description + base::NativeLibrary library_; // The opened library reference. + NPPluginFuncs plugin_funcs_; // The struct of plugin side functions. + bool initialized_; // Is the plugin initialized? + NPSavedData *saved_data_; // Persisted plugin info for NPAPI. + int instance_count_; // Count of plugins in use. + bool skip_unload_; // True if library_ should not be unloaded. + + // Function pointers to entry points into the plugin. + PluginEntryPoints entry_points_; + + DISALLOW_COPY_AND_ASSIGN(PluginLib); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_LIB_H_ diff --git a/webkit/glue/plugins/plugin_lib_mac.mm b/webkit/glue/plugins/plugin_lib_mac.mm new file mode 100644 index 0000000..263b3b4 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_mac.mm @@ -0,0 +1,346 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Carbon/Carbon.h> + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/native_library.h" +#include "base/scoped_cftyperef.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_list.h" + +static const short kSTRTypeDefinitionResourceID = 128; +static const short kSTRTypeDescriptionResourceID = 127; +static const short kSTRPluginDescriptionResourceID = 126; + +namespace NPAPI +{ + +namespace { + +NSDictionary* GetMIMETypes(CFBundleRef bundle) { + NSString* mime_filename = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginMIMETypesFilename")); + + if (mime_filename) { + + // get the file + + NSString* mime_path = + [NSString stringWithFormat:@"%@/Library/Preferences/%@", + NSHomeDirectory(), mime_filename]; + NSDictionary* mime_file_dict = + [NSDictionary dictionaryWithContentsOfFile:mime_path]; + + // is it valid? + + bool valid_file = false; + if (mime_file_dict) { + NSString* l10n_name = + [mime_file_dict objectForKey:@"WebPluginLocalizationName"]; + NSString* preferred_l10n = [[NSLocale currentLocale] localeIdentifier]; + if ([l10n_name isEqualToString:preferred_l10n]) + valid_file = true; + } + + if (valid_file) + return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; + + // dammit, I didn't want to have to do this + + typedef void (*CreateMIMETypesPrefsPtr)(void); + CreateMIMETypesPrefsPtr create_prefs_file = + (CreateMIMETypesPrefsPtr)CFBundleGetFunctionPointerForName( + bundle, CFSTR("BP_CreatePluginMIMETypesPreferences")); + if (!create_prefs_file) + return nil; + create_prefs_file(); + + // one more time + + mime_file_dict = [NSDictionary dictionaryWithContentsOfFile:mime_path]; + if (mime_file_dict) + return [mime_file_dict objectForKey:@"WebPluginMIMETypes"]; + else + return nil; + + } else { + return (NSDictionary*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginMIMETypes")); + } +} + +bool ReadPlistPluginInfo(const FilePath& filename, CFBundleRef bundle, + WebPluginInfo* info) { + NSDictionary* mime_types = GetMIMETypes(bundle); + if (!mime_types) + return false; // no type info here; try elsewhere + + for (NSString* mime_type in [mime_types allKeys]) { + NSDictionary* mime_dict = [mime_types objectForKey:mime_type]; + NSString* mime_desc = [mime_dict objectForKey:@"WebPluginTypeDescription"]; + NSArray* mime_exts = [mime_dict objectForKey:@"WebPluginExtensions"]; + + WebPluginMimeType mime; + mime.mime_type = base::SysNSStringToUTF8([mime_type lowercaseString]); + // Remove PDF from the list of types handled by QuickTime, since it provides + // a worse experience than just downloading the PDF. + if (mime.mime_type == "application/pdf" && + StartsWithASCII(filename.BaseName().value(), "QuickTime", false)) { + continue; + } + + if (mime_desc) + mime.description = base::SysNSStringToUTF16(mime_desc); + for (NSString* ext in mime_exts) + mime.file_extensions.push_back( + base::SysNSStringToUTF8([ext lowercaseString])); + + info->mime_types.push_back(mime); + } + + NSString* plugin_name = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginName")); + NSString* plugin_vers = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("CFBundleShortVersionString")); + NSString* plugin_desc = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("WebPluginDescription")); + + if (plugin_name) + info->name = base::SysNSStringToUTF16(plugin_name); + else + info->name = UTF8ToUTF16(filename.BaseName().value()); + info->path = filename; + if (plugin_vers) + info->version = base::SysNSStringToUTF16(plugin_vers); + if (plugin_desc) + info->desc = base::SysNSStringToUTF16(plugin_desc); + else + info->desc = UTF8ToUTF16(filename.BaseName().value()); + info->enabled = true; + + return true; +} + +class ScopedBundleResourceFile { + public: + ScopedBundleResourceFile(CFBundleRef bundle) : bundle_(bundle) { + old_ref_num_ = CurResFile(); + bundle_ref_num_ = CFBundleOpenBundleResourceMap(bundle); + UseResFile(bundle_ref_num_); + } + ~ScopedBundleResourceFile() { + UseResFile(old_ref_num_); + CFBundleCloseBundleResourceMap(bundle_, bundle_ref_num_); + } + + private: + CFBundleRef bundle_; + CFBundleRefNum bundle_ref_num_; + ResFileRefNum old_ref_num_; +}; + +bool GetSTRResource(CFBundleRef bundle, short res_id, + std::vector<std::string>* contents) { + Handle res_handle = Get1Resource('STR#', res_id); + if (!res_handle || !*res_handle) + return false; + + char* pointer = *res_handle; + short num_strings = *(short*)pointer; + pointer += sizeof(short); + for (short i = 0; i < num_strings; ++i) { + // Despite being 8-bits wide, these are legacy encoded. Make a round trip. + scoped_cftyperef<CFStringRef> str(CFStringCreateWithPascalStringNoCopy( + kCFAllocatorDefault, + (unsigned char*)pointer, + GetApplicationTextEncoding(), // is this right? + kCFAllocatorNull)); // perhaps CFStringGetSystemEncoding? + if (!str.get()) + return false; + contents->push_back(base::SysCFStringRefToUTF8(str.get())); + pointer += 1+*reinterpret_cast<unsigned char*>(pointer); + } + + return true; +} + +bool ReadSTRPluginInfo(const FilePath& filename, CFBundleRef bundle, + WebPluginInfo* info) { + ScopedBundleResourceFile res_file(bundle); + + std::vector<std::string> type_strings; + if (!GetSTRResource(bundle, kSTRTypeDefinitionResourceID, &type_strings)) + return false; + + std::vector<std::string> type_descs; + bool have_type_descs = GetSTRResource(bundle, + kSTRTypeDescriptionResourceID, + &type_descs); + + std::vector<std::string> plugin_descs; + bool have_plugin_descs = GetSTRResource(bundle, + kSTRPluginDescriptionResourceID, + &plugin_descs); + + size_t num_types = type_strings.size()/2; + + for (size_t i = 0; i < num_types; ++i) { + WebPluginMimeType mime; + mime.mime_type = StringToLowerASCII(type_strings[2*i]); + if (have_type_descs && i < type_descs.size()) + mime.description = UTF8ToUTF16(type_descs[i]); + SplitString(StringToLowerASCII(type_strings[2*i+1]), ',', + &mime.file_extensions); + + info->mime_types.push_back(mime); + } + + NSString* plugin_vers = + (NSString*)CFBundleGetValueForInfoDictionaryKey(bundle, + CFSTR("CFBundleShortVersionString")); + + if (have_plugin_descs && plugin_descs.size() > 1) + info->name = UTF8ToUTF16(plugin_descs[1]); + else + info->name = UTF8ToUTF16(filename.BaseName().value()); + info->path = filename; + if (plugin_vers) + info->version = base::SysNSStringToUTF16(plugin_vers); + if (have_plugin_descs && plugin_descs.size() > 0) + info->desc = UTF8ToUTF16(plugin_descs[0]); + else + info->desc = UTF8ToUTF16(filename.BaseName().value()); + info->enabled = true; + + return true; +} + +} // anonymous namespace + +bool PluginLib::ReadWebPluginInfo(const FilePath &filename, + WebPluginInfo* info) { + // There are two ways to get information about plugin capabilities. One is an + // Info.plist set of keys, documented at + // http://developer.apple.com/documentation/InternetWeb/Conceptual/WebKit_PluginProgTopic/Concepts/AboutPlugins.html . + // The other is a set of STR# resources, documented at + // https://developer.mozilla.org/En/Gecko_Plugin_API_Reference/Plug-in_Development_Overview . + // + // Historically, the data was maintained in the STR# resources. Apple, with + // the introduction of WebKit, noted the weaknesses of resources and moved the + // information into the Info.plist. Mozilla had always supported a + // NP_GetMIMEDescription() entry point for Unix plugins and also supports it + // on the Mac to supplement the STR# format. WebKit does not support + // NP_GetMIMEDescription() and neither do we. (That entry point is documented + // at https://developer.mozilla.org/en/NP_GetMIMEDescription .) We prefer the + // Info.plist format because it's more extensible and has a defined encoding, + // but will fall back to the STR# format of the data if it is not present in + // the Info.plist. + // + // The parsing of the data lives in the two functions ReadSTRPluginInfo() and + // ReadPlistPluginInfo(), but a summary of the formats follows. + // + // Each data type handled by a plugin has several properties: + // - <<type0mimetype>> + // - <<type0fileextension0>>..<<type0fileextensionk>> + // - <<type0description>> + // + // Each plugin may have any number of types defined. In addition, the plugin + // itself has properties: + // - <<plugindescription>> + // - <<pluginname>> + // + // For the Info.plist version, the data is formatted as follows (in text plist + // format): + // { + // ... the usual plist keys ... + // WebPluginDescription = <<plugindescription>>; + // WebPluginMIMETypes = { + // <<type0mimetype>> = { + // WebPluginExtensions = ( + // <<type0fileextension0>>, + // ... + // <<type0fileextensionk>>, + // ); + // WebPluginTypeDescription = <<type0description>>; + // }; + // <<type1mimetype>> = { ... }; + // ... + // <<typenmimetype>> = { ... }; + // }; + // WebPluginName = <<pluginname>>; + // } + // + // Alternatively (and this is undocumented), rather than a WebPluginMIMETypes + // key, there may be a WebPluginMIMETypesFilename key. If it is present, then + // it is the name of a file in the user's preferences folder in which to find + // the WebPluginMIMETypes key. If the key is present but the file doesn't + // exist, we must load the plugin and call a specific function to have the + // plugin create the file. + // + // If we do not find those keys in the Info.plist, we fall back to the STR# + // resources. In them, the data is formatted as follows: + // STR# 128 + // (1) <<type0mimetype>> + // (2) <<type0fileextension0>>,...,<<type0fileextensionk>> + // (3) <<type1mimetype>> + // (4) <<type1fileextension0>>,...,<<type1fileextensionk>> + // (...) + // (2n+1) <<typenmimetype>> + // (2n+2) <<typenfileextension0>>,...,<<typenfileextensionk>> + // STR# 127 + // (1) <<type0description>> + // (2) <<type1description>> + // (...) + // (n+1) <<typendescription>> + // STR# 126 + // (1) <<plugindescription>> + // (2) <<pluginname>> + // + // Strictly speaking, only STR# 128 is required. + + scoped_cftyperef<CFURLRef> bundle_url(CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)filename.value().c_str(), + filename.value().length(), true)); + if (!bundle_url) + return false; + scoped_cftyperef<CFBundleRef> bundle(CFBundleCreate(kCFAllocatorDefault, + bundle_url.get())); + if (!bundle) + return false; + + // preflight + + OSType type = 0; + CFBundleGetPackageInfo(bundle.get(), &type, NULL); + if (type != FOUR_CHAR_CODE('BRPL')) + return false; + + CFErrorRef error; + Boolean would_load = CFBundlePreflightExecutable(bundle.get(), &error); + if (!would_load) + return false; + + // get the info + + if (ReadPlistPluginInfo(filename, bundle.get(), info)) + return true; + + if (ReadSTRPluginInfo(filename, bundle.get(), info)) + return true; + + // ... or not + + return false; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib_posix.cc b/webkit/glue/plugins/plugin_lib_posix.cc new file mode 100644 index 0000000..dbc64ed --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_posix.cc @@ -0,0 +1,255 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_lib.h" + +#include <dlfcn.h> +#if defined(OS_OPENBSD) +#include <sys/exec_elf.h> +#else +#include <elf.h> +#include <fcntl.h> +#endif +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/eintr_wrapper.h" +#include "base/file_util.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_list.h" + +// These headers must be included in this order to make the declaration gods +// happy. +#include "base/third_party/nspr/prcpucfg_linux.h" + +namespace { + +using NPAPI::PluginList; + +// Copied from nsplugindefs.h instead of including the file since it has a bunch +// of dependencies. +enum nsPluginVariable { + nsPluginVariable_NameString = 1, + nsPluginVariable_DescriptionString = 2 +}; + +// Read the ELF header and return true if it is usable on +// the current architecture (e.g. 32-bit ELF on 32-bit build). +// Returns false on other errors as well. +bool ELFMatchesCurrentArchitecture(const FilePath& filename) { + // First make sure we can open the file and it is in fact, a regular file. + struct stat stat_buf; + // Open with O_NONBLOCK so we don't block on pipes. + int fd = open(filename.value().c_str(), O_RDONLY|O_NONBLOCK); + if (fd < 0) + return false; + bool ret = (fstat(fd, &stat_buf) >= 0 && S_ISREG(stat_buf.st_mode)); + if (HANDLE_EINTR(close(fd)) < 0) + return false; + if (!ret) + return false; + + const size_t kELFBufferSize = 5; + char buffer[kELFBufferSize]; + if (!file_util::ReadFile(filename, buffer, kELFBufferSize)) + return false; + + if (buffer[0] != ELFMAG0 || + buffer[1] != ELFMAG1 || + buffer[2] != ELFMAG2 || + buffer[3] != ELFMAG3) { + // Not an ELF file, perhaps? + return false; + } + + int elf_class = buffer[EI_CLASS]; +#if defined(ARCH_CPU_32_BITS) + if (elf_class == ELFCLASS32) + return true; +#elif defined(ARCH_CPU_64_BITS) + if (elf_class == ELFCLASS64) + return true; +#endif + + return false; +} + +// This structure matches enough of nspluginwrapper's NPW_PluginInfo +// for us to extract the real plugin path. +struct __attribute__((packed)) NSPluginWrapperInfo { + char ident[32]; // NSPluginWrapper magic identifier (includes version). + char path[PATH_MAX]; // Path to wrapped plugin. +}; + +// Test a plugin for whether it's been wrapped by NSPluginWrapper, and +// if so attempt to unwrap it. Pass in an opened plugin handle; on +// success, |dl| and |unwrapped_path| will be filled in with the newly +// opened plugin. On failure, params are left unmodified. +void UnwrapNSPluginWrapper(void **dl, FilePath* unwrapped_path) { + NSPluginWrapperInfo* info = + reinterpret_cast<NSPluginWrapperInfo*>(dlsym(*dl, "NPW_Plugin")); + if (!info) + return; // Not a NSPW plugin. + + // Here we could check the NSPW ident field for the versioning + // information, but the path field is available in all versions + // anyway. + + // Grab the path to the wrapped plugin. Just in case the structure + // format changes, protect against the path not being null-terminated. + char* path_end = static_cast<char*>(memchr(info->path, '\0', + sizeof(info->path))); + if (!path_end) + path_end = info->path + sizeof(info->path); + FilePath path = FilePath(std::string(info->path, path_end - info->path)); + + if (!ELFMatchesCurrentArchitecture(path)) { + LOG(WARNING) << path.value() << " is nspluginwrapper wrapping a " + << "plugin for a different architecture; it will " + << "work better if you instead use a native plugin."; + return; + } + + void* newdl = base::LoadNativeLibrary(path); + if (!newdl) { + // We couldn't load the unwrapped plugin for some reason, despite + // being able to load the wrapped one. Just use the wrapped one. + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Could not use unwrapped nspluginwrapper plugin " + << unwrapped_path->value() << ", using the wrapped one."; + return; + } + + // Unload the wrapped plugin, and use the wrapped plugin instead. + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Using unwrapped version " << unwrapped_path->value() + << " of nspluginwrapper-wrapped plugin."; + base::UnloadNativeLibrary(*dl); + *dl = newdl; + *unwrapped_path = path; +} + +} // anonymous namespace + +namespace NPAPI { + +bool PluginLib::ReadWebPluginInfo(const FilePath& filename, + WebPluginInfo* info) { + // The file to reference is: + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUnix.cpp + + // Skip files that aren't appropriate for our architecture. + if (!ELFMatchesCurrentArchitecture(filename)) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Skipping plugin " << filename.value() + << " because it doesn't match the current architecture."; + return false; + } + + void* dl = base::LoadNativeLibrary(filename); + if (!dl) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "While reading plugin info, unable to load library " + << filename.value() << ", skipping."; + return false; + } + + info->path = filename; + info->enabled = true; + + // Attempt to swap in the wrapped plugin if this is nspluginwrapper. + UnwrapNSPluginWrapper(&dl, &info->path); + + // See comments in plugin_lib_mac regarding this symbol. + typedef const char* (*NP_GetMimeDescriptionType)(); + NP_GetMimeDescriptionType NP_GetMIMEDescription = + reinterpret_cast<NP_GetMimeDescriptionType>( + dlsym(dl, "NP_GetMIMEDescription")); + const char* mime_description = NULL; + if (NP_GetMIMEDescription) + mime_description = NP_GetMIMEDescription(); + + if (mime_description) + ParseMIMEDescription(mime_description, &info->mime_types); + + // The plugin name and description live behind NP_GetValue calls. + typedef NPError (*NP_GetValueType)(void* unused, + nsPluginVariable variable, + void* value_out); + NP_GetValueType NP_GetValue = + reinterpret_cast<NP_GetValueType>(dlsym(dl, "NP_GetValue")); + if (NP_GetValue) { + const char* name = NULL; + NP_GetValue(NULL, nsPluginVariable_NameString, &name); + if (name) + info->name = UTF8ToUTF16(name); + + const char* description = NULL; + NP_GetValue(NULL, nsPluginVariable_DescriptionString, &description); + if (description) + info->desc = UTF8ToUTF16(description); + + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Got info for plugin " << filename.value() + << " Name = \"" << UTF16ToUTF8(info->name) + << "\", Description = \"" << UTF16ToUTF8(info->desc) << "\"."; + } else { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Plugin " << filename.value() + << " has no GetValue() and probably won't work."; + } + + // Intentionally not unloading the plugin here, it can lead to crashes. + + return true; +} + +// static +void PluginLib::ParseMIMEDescription( + const std::string& description, + std::vector<WebPluginMimeType>* mime_types) { + // We parse the description here into WebPluginMimeType structures. + // Naively from the NPAPI docs you'd think you could use + // string-splitting, but the Firefox parser turns out to do something + // different: find the first colon, then the second, then a semi. + // + // See ParsePluginMimeDescription near + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginsDirUtils.h#53 + + std::string::size_type ofs = 0; + for (;;) { + WebPluginMimeType mime_type; + + std::string::size_type end = description.find(':', ofs); + if (end == std::string::npos) + break; + mime_type.mime_type = description.substr(ofs, end - ofs); + ofs = end + 1; + + end = description.find(':', ofs); + if (end == std::string::npos) + break; + const std::string extensions = description.substr(ofs, end - ofs); + SplitString(extensions, ',', &mime_type.file_extensions); + ofs = end + 1; + + end = description.find(';', ofs); + // It's ok for end to run off the string here. If there's no + // trailing semicolon we consume the remainder of the string. + if (end != std::string::npos) { + mime_type.description = UTF8ToUTF16(description.substr(ofs, end - ofs)); + } else { + mime_type.description = UTF8ToUTF16(description.substr(ofs)); + } + mime_types->push_back(mime_type); + if (end == std::string::npos) + break; + ofs = end + 1; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_lib_unittest.cc b/webkit/glue/plugins/plugin_lib_unittest.cc new file mode 100644 index 0000000..a52510b --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/plugin_lib.h" + +#include "base/string_util.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Test the unloading of plugin libs. Bug http://crbug.com/46526 showed that +// if UnloadAllPlugins() simply iterates through the g_loaded_libs global +// variable, we can get a crash if no plugin libs were marked as always loaded. +class PluginLibTest : public NPAPI::PluginLib { + public: + PluginLibTest() : NPAPI::PluginLib(WebPluginInfo(), NULL) { + } + using NPAPI::PluginLib::Unload; +}; + +TEST(PluginLibLoading, UnloadAllPlugins) { + // For the creation of the g_loaded_libs global variable. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Try with a single plugin lib. + scoped_refptr<PluginLibTest> plugin_lib1 = new PluginLibTest(); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Try with two plugin libs. + plugin_lib1 = new PluginLibTest(); + scoped_refptr<PluginLibTest> plugin_lib2 = new PluginLibTest(); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Now try to manually Unload one and then UnloadAll. + plugin_lib1 = new PluginLibTest(); + plugin_lib2 = new PluginLibTest(); + plugin_lib1->Unload(); + NPAPI::PluginLib::UnloadAllPlugins(); + + // Need to create it again, it should have been destroyed above. + ASSERT_EQ(static_cast<PluginLibTest*>(NULL), + PluginLibTest::CreatePluginLib(FilePath())); + + // Now try to manually Unload the only one and then UnloadAll. + plugin_lib1 = new PluginLibTest(); + plugin_lib1->Unload(); + NPAPI::PluginLib::UnloadAllPlugins(); +} + +#if defined(OS_LINUX) + +// Test parsing a simple description: Real Audio. +TEST(MIMEDescriptionParse, Simple) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "audio/x-pn-realaudio-plugin:rpm:RealAudio document;", + &types); + ASSERT_EQ(1U, types.size()); + const WebPluginMimeType& type = types[0]; + EXPECT_EQ("audio/x-pn-realaudio-plugin", type.mime_type); + ASSERT_EQ(1U, type.file_extensions.size()); + EXPECT_EQ("rpm", type.file_extensions[0]); + EXPECT_EQ(ASCIIToUTF16("RealAudio document"), type.description); +} + +// Test parsing a multi-entry description: QuickTime as provided by Totem. +TEST(MIMEDescriptionParse, Multi) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "video/quicktime:mov:QuickTime video;video/mp4:mp4:MPEG-4 " + "video;image/x-macpaint:pntg:MacPaint Bitmap image;image/x" + "-quicktime:pict, pict1, pict2:QuickTime image;video/x-m4v" + ":m4v:MPEG-4 video;", + &types); + + ASSERT_EQ(5U, types.size()); + + // Check the x-quicktime one, since it looks tricky with spaces in the + // extension list. + const WebPluginMimeType& type = types[3]; + EXPECT_EQ("image/x-quicktime", type.mime_type); + ASSERT_EQ(3U, type.file_extensions.size()); + EXPECT_EQ("pict2", type.file_extensions[2]); + EXPECT_EQ(ASCIIToUTF16("QuickTime image"), type.description); +} + +// Test parsing a Japanese description, since we got this wrong in the past. +// This comes from loading Totem with LANG=ja_JP.UTF-8. +TEST(MIMEDescriptionParse, JapaneseUTF8) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "audio/x-ogg:ogg:Ogg \xe3\x82\xaa\xe3\x83\xbc\xe3\x83\x87" + "\xe3\x82\xa3\xe3\x83\xaa", + &types); + + ASSERT_EQ(1U, types.size()); + // Check we got the right number of Unicode characters out of the parse. + EXPECT_EQ(9U, types[0].description.size()); +} + +// Test that we handle corner cases gracefully. +TEST(MIMEDescriptionParse, CornerCases) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription("mime/type:", &types); + EXPECT_TRUE(types.empty()); + + types.clear(); + NPAPI::PluginLib::ParseMIMEDescription("mime/type:ext1:", &types); + ASSERT_EQ(1U, types.size()); + EXPECT_EQ("mime/type", types[0].mime_type); + EXPECT_EQ(1U, types[0].file_extensions.size()); + EXPECT_EQ("ext1", types[0].file_extensions[0]); + EXPECT_EQ(string16(), types[0].description); +} + +// This Java plugin has embedded semicolons in the mime type. +TEST(MIMEDescriptionParse, ComplicatedJava) { + std::vector<WebPluginMimeType> types; + NPAPI::PluginLib::ParseMIMEDescription( + "application/x-java-vm:class,jar:IcedTea;application/x-java" + "-applet:class,jar:IcedTea;application/x-java-applet;versio" + "n=1.1:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.1:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.2:class,jar:IcedTea;application/x-java-applet;version=" + "1.1.3:class,jar:IcedTea;application/x-java-applet;version=" + "1.2:class,jar:IcedTea;application/x-java-applet;version=1." + "2.1:class,jar:IcedTea;application/x-java-applet;version=1." + "2.2:class,jar:IcedTea;application/x-java-applet;version=1." + "3:class,jar:IcedTea;application/x-java-applet;version=1.3." + "1:class,jar:IcedTea;application/x-java-applet;version=1.4:" + "class,jar:IcedTea", + &types); + + ASSERT_EQ(12U, types.size()); + for (size_t i = 0; i < types.size(); ++i) + EXPECT_EQ(ASCIIToUTF16("IcedTea"), types[i].description); + + // Verify that the mime types with semis are coming through ok. + EXPECT_TRUE(types[4].mime_type.find(';') != std::string::npos); +} + +#endif // defined(OS_LINUX) diff --git a/webkit/glue/plugins/plugin_lib_win.cc b/webkit/glue/plugins/plugin_lib_win.cc new file mode 100644 index 0000000..00f6243 --- /dev/null +++ b/webkit/glue/plugins/plugin_lib_win.cc @@ -0,0 +1,41 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/plugin_lib.h" + +#include "base/file_version_info.h" +#include "base/file_version_info_win.h" +#include "base/path_service.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_list.h" + +namespace NPAPI +{ +bool PluginLib::ReadWebPluginInfo(const FilePath &filename, + WebPluginInfo* info) { + // On windows, the way we get the mime types for the library is + // to check the version information in the DLL itself. This + // will be a string of the format: <type1>|<type2>|<type3>|... + // For example: + // video/quicktime|audio/aiff|image/jpeg + scoped_ptr<FileVersionInfo> version_info( + FileVersionInfo::CreateFileVersionInfo(filename.value())); + if (!version_info.get()) + return false; + + FileVersionInfoWin* version_info_win = + static_cast<FileVersionInfoWin*>(version_info.get()); + PluginVersionInfo pvi; + pvi.mime_types = version_info_win->GetStringValue(L"MIMEType"); + pvi.file_extensions = version_info_win->GetStringValue(L"FileExtents"); + pvi.type_descriptions = version_info_win->GetStringValue(L"FileOpenName"); + pvi.product_name = version_info->product_name(); + pvi.file_description = version_info->file_description(); + pvi.file_version = version_info->file_version(); + pvi.path = filename; + + return PluginList::CreateWebPluginInfo(pvi, info); +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list.cc b/webkit/glue/plugins/plugin_list.cc new file mode 100644 index 0000000..a3412d7 --- /dev/null +++ b/webkit/glue/plugins/plugin_list.cc @@ -0,0 +1,460 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_list.h" + +#include <algorithm> + +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "net/base/mime_util.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_switches.h" +#include "webkit/glue/webkit_glue.h" + +namespace NPAPI { + +base::LazyInstance<PluginList> g_singleton(base::LINKER_INITIALIZED); + +// static +PluginList* PluginList::Singleton() { + return g_singleton.Pointer(); +} + +// static +bool PluginList::DebugPluginLoading() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDebugPluginLoading); +} + +bool PluginList::PluginsLoaded() { + AutoLock lock(lock_); + return plugins_loaded_; +} + +void PluginList::RefreshPlugins() { + AutoLock lock(lock_); + plugins_need_refresh_ = true; +} + +void PluginList::AddExtraPluginPath(const FilePath& plugin_path) { + AutoLock lock(lock_); + extra_plugin_paths_.push_back(plugin_path); +} + +void PluginList::RemoveExtraPluginPath(const FilePath& plugin_path) { + AutoLock lock(lock_); + std::vector<FilePath>::iterator it = + std::find(extra_plugin_paths_.begin(), extra_plugin_paths_.end(), + plugin_path); + if (it != extra_plugin_paths_.end()) + extra_plugin_paths_.erase(it); +} + +void PluginList::AddExtraPluginDir(const FilePath& plugin_dir) { + AutoLock lock(lock_); + extra_plugin_dirs_.push_back(plugin_dir); +} + +void PluginList::RegisterInternalPlugin(const PluginVersionInfo& info) { + AutoLock lock(lock_); + internal_plugins_.push_back(info); +} + +void PluginList::UnregisterInternalPlugin(const FilePath& path) { + AutoLock lock(lock_); + for (size_t i = 0; i < internal_plugins_.size(); i++) { + if (internal_plugins_[i].path == path) { + internal_plugins_.erase(internal_plugins_.begin() + i); + return; + } + } + NOTREACHED(); +} + +bool PluginList::ReadPluginInfo(const FilePath& filename, + WebPluginInfo* info, + const PluginEntryPoints** entry_points) { + { + AutoLock lock(lock_); + for (size_t i = 0; i < internal_plugins_.size(); ++i) { + if (filename == internal_plugins_[i].path) { + *entry_points = &internal_plugins_[i].entry_points; + return CreateWebPluginInfo(internal_plugins_[i], info); + } + } + } + + // Not an internal plugin. + *entry_points = NULL; + + return PluginLib::ReadWebPluginInfo(filename, info); +} + +bool PluginList::CreateWebPluginInfo(const PluginVersionInfo& pvi, + WebPluginInfo* info) { + std::vector<std::string> mime_types, file_extensions; + std::vector<string16> descriptions; + SplitString(WideToUTF8(pvi.mime_types), '|', &mime_types); + SplitString(WideToUTF8(pvi.file_extensions), '|', &file_extensions); + SplitString(WideToUTF16(pvi.type_descriptions), '|', &descriptions); + + info->mime_types.clear(); + + if (mime_types.empty()) + return false; + + info->name = WideToUTF16(pvi.product_name); + info->desc = WideToUTF16(pvi.file_description); + info->version = WideToUTF16(pvi.file_version); + info->path = pvi.path; + info->enabled = true; + + for (size_t i = 0; i < mime_types.size(); ++i) { + WebPluginMimeType mime_type; + mime_type.mime_type = StringToLowerASCII(mime_types[i]); + if (file_extensions.size() > i) + SplitString(file_extensions[i], ',', &mime_type.file_extensions); + + if (descriptions.size() > i) { + mime_type.description = descriptions[i]; + + // On Windows, the description likely has a list of file extensions + // embedded in it (e.g. "SurfWriter file (*.swr)"). Remove an extension + // list from the description if it is present. + size_t ext = mime_type.description.find(ASCIIToUTF16("(*")); + if (ext != string16::npos) { + if (ext > 1 && mime_type.description[ext -1] == ' ') + ext--; + + mime_type.description.erase(ext); + } + } + + info->mime_types.push_back(mime_type); + } + + return true; +} + +PluginList::PluginList() + : plugins_loaded_(false), plugins_need_refresh_(false) { + PlatformInit(); +} + +void PluginList::LoadPlugins(bool refresh) { + // Don't want to hold the lock while loading new plugins, so we don't block + // other methods if they're called on other threads. + std::vector<FilePath> extra_plugin_paths; + std::vector<FilePath> extra_plugin_dirs; + std::vector<PluginVersionInfo> internal_plugins; + { + AutoLock lock(lock_); + if (plugins_loaded_ && !refresh && !plugins_need_refresh_) + return; + + // Clear the refresh bit now, because it might get set again before we + // reach the end of the method. + plugins_need_refresh_ = false; + extra_plugin_paths = extra_plugin_paths_; + extra_plugin_dirs = extra_plugin_dirs_; + internal_plugins = internal_plugins_; + } + + base::TimeTicks start_time = base::TimeTicks::Now(); + + std::vector<WebPluginInfo> new_plugins; + std::set<FilePath> visited_plugins; + + std::vector<FilePath> directories_to_scan; + GetPluginDirectories(&directories_to_scan); + + // Load internal plugins first so that, if both an internal plugin and a + // "discovered" plugin want to handle the same type, the internal plugin + // will have precedence. + for (size_t i = 0; i < internal_plugins.size(); ++i) { + if (internal_plugins[i].path.value() == kDefaultPluginLibraryName) + continue; + LoadPlugin(internal_plugins[i].path, &new_plugins); + } + + for (size_t i = 0; i < extra_plugin_paths.size(); ++i) { + const FilePath& path = extra_plugin_paths[i]; + if (visited_plugins.find(path) != visited_plugins.end()) + continue; + LoadPlugin(path, &new_plugins); + visited_plugins.insert(path); + } + + for (size_t i = 0; i < extra_plugin_dirs.size(); ++i) { + LoadPluginsFromDir(extra_plugin_dirs[i], &new_plugins, &visited_plugins); + } + + for (size_t i = 0; i < directories_to_scan.size(); ++i) { + LoadPluginsFromDir(directories_to_scan[i], &new_plugins, &visited_plugins); + } + +#if defined OS_WIN + LoadPluginsFromRegistry(&new_plugins, &visited_plugins); +#endif + + // Load the default plugin last. + if (webkit_glue::IsDefaultPluginEnabled()) + LoadPlugin(FilePath(kDefaultPluginLibraryName), &new_plugins); + + base::TimeTicks end_time = base::TimeTicks::Now(); + base::TimeDelta elapsed = end_time - start_time; + DLOG(INFO) << "Loaded plugin list in " << elapsed.InMilliseconds() << " ms."; + + // Only update the data now since loading plugins can take a while. + AutoLock lock(lock_); + + // Go through and mark new plugins in the disabled list as, well, disabled. + for (std::vector<WebPluginInfo>::iterator it = new_plugins.begin(); + it != new_plugins.end(); + ++it) { + if (disabled_plugins_.find(it->path) != disabled_plugins_.end()) + it->enabled = false; + } + + plugins_ = new_plugins; + plugins_loaded_ = true; +} + +void PluginList::LoadPlugin(const FilePath& path, + std::vector<WebPluginInfo>* plugins) { + WebPluginInfo plugin_info; + const PluginEntryPoints* entry_points; + + if (!ReadPluginInfo(path, &plugin_info, &entry_points)) + return; + + if (!ShouldLoadPlugin(plugin_info, plugins)) + return; + + if (path.value() != kDefaultPluginLibraryName +#if defined(OS_WIN) && !defined(NDEBUG) + && path.BaseName().value() != L"npspy.dll" // Make an exception for NPSPY +#endif + ) { + for (size_t i = 0; i < plugin_info.mime_types.size(); ++i) { + // TODO: don't load global handlers for now. + // WebKit hands to the Plugin before it tries + // to handle mimeTypes on its own. + const std::string &mime_type = plugin_info.mime_types[i].mime_type; + if (mime_type == "*" ) + return; + } + } + + plugins->push_back(plugin_info); +} + +bool PluginList::FindPlugin(const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info) { + DCHECK(mime_type == StringToLowerASCII(mime_type)); + + LoadPlugins(false); + AutoLock lock(lock_); + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].enabled && + SupportsType(plugins_[i], mime_type, allow_wildcard)) { + *info = plugins_[i]; + return true; + } + } + + return false; +} + +bool PluginList::FindDisabledPlugin(const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info) { + DCHECK(mime_type == StringToLowerASCII(mime_type)); + + LoadPlugins(false); + AutoLock lock(lock_); + for (size_t i = 0; i < plugins_.size(); ++i) { + if (!plugins_[i].enabled && + SupportsType(plugins_[i], mime_type, allow_wildcard)) { + *info = plugins_[i]; + return true; + } + } + + return false; +} + +bool PluginList::FindPlugin(const GURL &url, + std::string* actual_mime_type, + WebPluginInfo* info) { + LoadPlugins(false); + AutoLock lock(lock_); + std::string path = url.path(); + std::string::size_type last_dot = path.rfind('.'); + if (last_dot == std::string::npos) + return false; + + std::string extension = StringToLowerASCII(std::string(path, last_dot+1)); + + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].enabled && + SupportsExtension(plugins_[i], extension, actual_mime_type)) { + *info = plugins_[i]; + return true; + } + } + + return false; +} + +bool PluginList::SupportsType(const WebPluginInfo& info, + const std::string &mime_type, + bool allow_wildcard) { + // Webkit will ask for a plugin to handle empty mime types. + if (mime_type.empty()) + return false; + + for (size_t i = 0; i < info.mime_types.size(); ++i) { + const WebPluginMimeType& mime_info = info.mime_types[i]; + if (net::MatchesMimeType(mime_info.mime_type, mime_type)) { + if (!allow_wildcard && mime_info.mime_type == "*") { + continue; + } + return true; + } + } + return false; +} + +bool PluginList::SupportsExtension(const WebPluginInfo& info, + const std::string &extension, + std::string* actual_mime_type) { + for (size_t i = 0; i < info.mime_types.size(); ++i) { + const WebPluginMimeType& mime_type = info.mime_types[i]; + for (size_t j = 0; j < mime_type.file_extensions.size(); ++j) { + if (mime_type.file_extensions[j] == extension) { + if (actual_mime_type) + *actual_mime_type = mime_type.mime_type; + return true; + } + } + } + + return false; +} + + +void PluginList::GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins) { + LoadPlugins(refresh); + + AutoLock lock(lock_); + *plugins = plugins_; +} + +void PluginList::GetEnabledPlugins(bool refresh, + std::vector<WebPluginInfo>* plugins) { + LoadPlugins(refresh); + + plugins->clear(); + AutoLock lock(lock_); + for (std::vector<WebPluginInfo>::const_iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->enabled) + plugins->push_back(*it); + } +} + +bool PluginList::GetPluginInfo(const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info, + std::string* actual_mime_type) { + bool found = FindPlugin(mime_type, allow_wildcard, info); + if (!found || (info->path.value() == kDefaultPluginLibraryName)) { + if (FindPlugin(url, actual_mime_type, info) || + FindDisabledPlugin(mime_type, allow_wildcard, info)) { + found = true; + } + } + + return found; +} + +bool PluginList::GetPluginInfoByPath(const FilePath& plugin_path, + WebPluginInfo* info) { + LoadPlugins(false); + AutoLock lock(lock_); + for (size_t i = 0; i < plugins_.size(); ++i) { + if (plugins_[i].path == plugin_path) { + *info = plugins_[i]; + return true; + } + } + + return false; +} + +bool PluginList::EnablePlugin(const FilePath& filename) { + AutoLock lock(lock_); + + bool did_enable = false; + + std::set<FilePath>::iterator entry = disabled_plugins_.find(filename); + if (entry == disabled_plugins_.end()) + return did_enable; // Early exit if plugin not in disabled list. + + disabled_plugins_.erase(entry); // Remove from disabled list. + + // Set enabled flags if necessary. + for (std::vector<WebPluginInfo>::iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->path == filename) { + DCHECK(!it->enabled); // Should have been disabled. + it->enabled = true; + did_enable = true; + } + } + + return did_enable; +} + +bool PluginList::DisablePlugin(const FilePath& filename) { + AutoLock lock(lock_); + + bool did_disable = false; + + if (disabled_plugins_.find(filename) != disabled_plugins_.end()) + return did_disable; // Early exit if plugin already in disabled list. + + disabled_plugins_.insert(filename); // Add to disabled list. + + // Unset enabled flags if necessary. + for (std::vector<WebPluginInfo>::iterator it = plugins_.begin(); + it != plugins_.end(); + ++it) { + if (it->path == filename) { + DCHECK(it->enabled); // Should have been enabled. + it->enabled = false; + did_disable = true; + } + } + + return did_disable; +} + +void PluginList::Shutdown() { + // TODO +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list.h b/webkit/glue/plugins/plugin_list.h new file mode 100644 index 0000000..ce9da28 --- /dev/null +++ b/webkit/glue/plugins/plugin_list.h @@ -0,0 +1,275 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PLUGIN_LIST_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_LIST_H_ + +#include <set> +#include <string> +#include <vector> +#include <set> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/lock.h" +#include "third_party/npapi/bindings/nphostapi.h" +#include "webkit/glue/plugins/webplugininfo.h" + +class GURL; + +namespace base { + +template <typename T> +struct DefaultLazyInstanceTraits; + +} // namespace base + +namespace NPAPI { + +#define kDefaultPluginLibraryName FILE_PATH_LITERAL("default_plugin") +#define kGearsPluginLibraryName FILE_PATH_LITERAL("gears") + +class PluginInstance; + +// This struct holds entry points into a plugin. The entry points are +// slightly different between Win/Mac and Unixes. +struct PluginEntryPoints { +#if !defined(OS_POSIX) || defined(OS_MACOSX) + NP_GetEntryPointsFunc np_getentrypoints; +#endif + NP_InitializeFunc np_initialize; + NP_ShutdownFunc np_shutdown; +}; + +// This struct fully describes a plugin. For external plugins, it's read in from +// the version info of the dll; For internal plugins, it's predefined and +// includes addresses of entry functions. (Yes, it's Win32 NPAPI-centric, but +// it'll do for holding descriptions of internal plugins cross-platform.) +struct PluginVersionInfo { + FilePath path; + // Info about the plugin itself. + std::wstring product_name; + std::wstring file_description; + std::wstring file_version; + // Info about the data types that the plugin supports. + std::wstring mime_types; + std::wstring file_extensions; + std::wstring type_descriptions; + // Entry points for internal plugins. Pointers are NULL for external plugins. + PluginEntryPoints entry_points; +}; + +// The PluginList is responsible for loading our NPAPI based plugins. It does +// so in whatever manner is appropriate for the platform. On Windows, it loads +// plugins from a known directory by looking for DLLs which start with "NP", +// and checking to see if they are valid NPAPI libraries. On the Mac, it walks +// the machine-wide and user plugin directories and loads anything that has +// the correct types. On Linux, it walks the plugin directories as well +// (e.g. /usr/lib/browser-plugins/). +// This object is thread safe. +class PluginList { + public: + // Gets the one instance of the PluginList. + static PluginList* Singleton(); + + // Returns true if we're in debug-plugin-loading mode. This is controlled + // by a command line switch. + static bool DebugPluginLoading(); + + // Returns true iff the plugin list has been loaded already. + bool PluginsLoaded(); + + // Cause the plugin list to refresh next time they are accessed, regardless + // of whether they are already loaded. + void RefreshPlugins(); + + // Add/Remove an extra plugin to load when we actually do the loading. Must + // be called before the plugins have been loaded. + void AddExtraPluginPath(const FilePath& plugin_path); + void RemoveExtraPluginPath(const FilePath& plugin_path); + + // Same as above, but specifies a directory in which to search for plugins. + void AddExtraPluginDir(const FilePath& plugin_dir); + + // Register an internal plugin with the specified plugin information and + // function pointers. An internal plugin must be registered before it can + // be loaded using PluginList::LoadPlugin(). + void RegisterInternalPlugin(const PluginVersionInfo& info); + + // Removes a specified internal plugin from the list. The search will match + // on the path from the version info previously registered. + // + // This is generally only necessary for tests. + void UnregisterInternalPlugin(const FilePath& path); + + // Creates a WebPluginInfo structure given a plugin's path. On success + // returns true, with the information being put into "info". If it's an + // internal plugin, "entry_points" is filled in as well with a + // internally-owned PluginEntryPoints pointer. + // Returns false if the library couldn't be found, or if it's not a plugin. + bool ReadPluginInfo(const FilePath& filename, + WebPluginInfo* info, + const PluginEntryPoints** entry_points); + + // Populate a WebPluginInfo from a PluginVersionInfo. + static bool CreateWebPluginInfo(const PluginVersionInfo& pvi, + WebPluginInfo* info); + + // Shutdown all plugins. Should be called at process teardown. + void Shutdown(); + + // Get all the plugins. + void GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins); + + // Get all the enabled plugins. + void GetEnabledPlugins(bool refresh, std::vector<WebPluginInfo>* plugins); + + // Returns true if a plugin is found for the given url and mime type + // (including disabled plugins, for which |info->enabled| is false). + // The mime type which corresponds to the URL is optionally returned + // back. + // The allow_wildcard parameter controls whether this function returns + // plugins which support wildcard mime types (* as the mime type). + bool GetPluginInfo(const GURL& url, + const std::string& mime_type, + bool allow_wildcard, + WebPluginInfo* info, + std::string* actual_mime_type); + + // Get plugin info by plugin path (including disabled plugins). Returns true + // if the plugin is found and WebPluginInfo has been filled in |info|. + bool GetPluginInfoByPath(const FilePath& plugin_path, + WebPluginInfo* info); + + // Load a specific plugin with full path. + void LoadPlugin(const FilePath& filename, + std::vector<WebPluginInfo>* plugins); + + // Enable a specific plugin, specified by path. Returns |true| iff a plugin + // currently in the plugin list was actually enabled as a result; regardless + // of return value, if a plugin is found in the future with the given name, it + // will be enabled. Note that plugins are enabled by default as far as + // |PluginList| is concerned. + bool EnablePlugin(const FilePath& filename); + + // Disable a specific plugin, specified by path. Returns |true| iff a plugin + // currently in the plugin list was actually disabled as a result; regardless + // of return value, if a plugin is found in the future with the given name, it + // will be disabled. + bool DisablePlugin(const FilePath& filename); + + private: + // Constructors are private for singletons + PluginList(); + + // Load all plugins from the default plugins directory + void LoadPlugins(bool refresh); + + // Load all plugins from a specific directory. + // |plugins| is updated with loaded plugin information. + // |visited_plugins| is updated with paths to all plugins that were considered + // (including those we didn't load) + void LoadPluginsFromDir(const FilePath& path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins); + + // Returns true if we should load the given plugin, or false otherwise. + // plugins is the list of plugins we have crawled in the current plugin + // loading run. + bool ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins); + + // Find a plugin by mime type; only searches enabled plugins. + // The allow_wildcard parameter controls whether this function returns + // plugins which support wildcard mime types (* as the mime type) + bool FindPlugin(const std::string &mime_type, + bool allow_wildcard, + WebPluginInfo* info); + + // Just like |FindPlugin| but it only looks at the disabled plug-ins. + bool FindDisabledPlugin(const std::string &mime_type, + bool allow_wildcard, + WebPluginInfo* info); + + // Find a plugin by extension; only searches enabled plugins. Returns the + // corresponding mime type. + bool FindPlugin(const GURL &url, + std::string* actual_mime_type, + WebPluginInfo* info); + + // Returns true if the given WebPluginInfo supports "mime-type". + // mime_type should be all lower case. + static bool SupportsType(const WebPluginInfo& info, + const std::string &mime_type, + bool allow_wildcard); + + // Returns true if the given WebPluginInfo supports a given file extension. + // extension should be all lower case. + // If mime_type is not NULL, it will be set to the mime type if found. + // The mime type which corresponds to the extension is optionally returned + // back. + static bool SupportsExtension(const WebPluginInfo& info, + const std::string &extension, + std::string* actual_mime_type); + + // + // Platform functions + // + + // Do any initialization. + void PlatformInit(); + + // Get the ordered list of directories from which to load plugins + void GetPluginDirectories(std::vector<FilePath>* plugin_dirs); + + // + // Command-line switches + // + +#if defined(OS_WIN) + // true if we shouldn't load the new WMP plugin. + bool dont_load_new_wmp_; + + // Loads plugins registered under HKCU\Software\MozillaPlugins and + // HKLM\Software\MozillaPlugins. + void LoadPluginsFromRegistry(std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins); +#endif + + // + // Internals + // + + bool plugins_loaded_; + + // If true, we reload plugins even if they've been loaded already. + bool plugins_need_refresh_; + + // Contains information about the available plugins. + std::vector<WebPluginInfo> plugins_; + + // Extra plugin paths that we want to search when loading. + std::vector<FilePath> extra_plugin_paths_; + + // Extra plugin directories that we want to search when loading. + std::vector<FilePath> extra_plugin_dirs_; + + // Holds information about internal plugins. + std::vector<PluginVersionInfo> internal_plugins_; + + // Path names of plugins to disable (the default is to enable them all). + std::set<FilePath> disabled_plugins_; + + // Need synchronization for the above members since this object can be + // accessed on multiple threads. + Lock lock_; + + friend struct base::DefaultLazyInstanceTraits<PluginList>; + + DISALLOW_COPY_AND_ASSIGN(PluginList); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_LIST_H_ diff --git a/webkit/glue/plugins/plugin_list_mac.mm b/webkit/glue/plugins/plugin_list_mac.mm new file mode 100644 index 0000000..16bde9d --- /dev/null +++ b/webkit/glue/plugins/plugin_list_mac.mm @@ -0,0 +1,107 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/plugin_list.h" + +#import <Foundation/Foundation.h> + +#include "base/file_util.h" +#include "base/mac_util.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "webkit/glue/plugins/plugin_lib.h" + +namespace { + +void GetPluginCommonDirectory(std::vector<FilePath>* plugin_dirs, + bool user) { + // Note that there are no NSSearchPathDirectory constants for these + // directories so we can't use Cocoa's NSSearchPathForDirectoriesInDomains(). + // Interestingly, Safari hard-codes the location (see + // WebKit/WebKit/mac/Plugins/WebPluginDatabase.mm's +_defaultPlugInPaths). + FSRef ref; + OSErr err = FSFindFolder(user ? kUserDomain : kLocalDomain, + kInternetPlugInFolderType, false, &ref); + + if (err) + return; + + plugin_dirs->push_back(FilePath(mac_util::PathFromFSRef(ref))); +} + +void GetPluginPrivateDirectory(std::vector<FilePath>* plugin_dirs) { + NSString* plugin_path = [[NSBundle mainBundle] builtInPlugInsPath]; + if (!plugin_path) + return; + + plugin_dirs->push_back(FilePath([plugin_path fileSystemRepresentation])); +} + +// Returns true if the plugin should be prevented from loading. +bool IsBlacklistedPlugin(const WebPluginInfo& info) { + // We blacklist Gears by included MIME type, since that is more stable than + // its name. Be careful about adding any more plugins to this list though, + // since it's easy to accidentally blacklist plugins that support lots of + // MIME types. + for (std::vector<WebPluginMimeType>::const_iterator i = + info.mime_types.begin(); i != info.mime_types.end(); ++i) { + // The Gears plugin is Safari-specific, so don't load it. + if (i->mime_type == "application/x-googlegears") + return true; + } + + return false; +} + +} // namespace + +namespace NPAPI +{ + +void PluginList::PlatformInit() { +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // Load from the user's area + GetPluginCommonDirectory(plugin_dirs, true); + + // Load from the machine-wide area + GetPluginCommonDirectory(plugin_dirs, false); + + // Load any bundled plugins (deprecated) + // TODO(stuartmorgan): Remove this once it's not used in TestShell. + GetPluginPrivateDirectory(plugin_dirs); +} + +void PluginList::LoadPluginsFromDir(const FilePath &path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + file_util::FileEnumerator enumerator(path, + false, // not recursive + file_util::FileEnumerator::DIRECTORIES); + for (FilePath path = enumerator.Next(); !path.value().empty(); + path = enumerator.Next()) { + LoadPlugin(path, plugins); + visited_plugins->insert(path); + } +} + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + if (IsBlacklistedPlugin(info)) + return false; + + // Hierarchy check + // (we're loading plugins hierarchically from Library folders, so plugins we + // encounter earlier must override plugins we encounter later) + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName() == info.path.BaseName()) { + return false; // We already have a loaded plugin higher in the hierarchy. + } + } + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list_posix.cc b/webkit/glue/plugins/plugin_list_posix.cc new file mode 100644 index 0000000..1fbd76f --- /dev/null +++ b/webkit/glue/plugins/plugin_list_posix.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_list.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "build/build_config.h" + +namespace { + +// We build up a list of files and mtimes so we can sort them. +typedef std::pair<FilePath, base::Time> FileAndTime; +typedef std::vector<FileAndTime> FileTimeList; + +// Comparator used to sort by descending mtime then ascending filename. +bool CompareTime(const FileAndTime& a, const FileAndTime& b) { + if (a.second == b.second) { + // Fall back on filename sorting, just to make the predicate valid. + return a.first < b.first; + } + + // Sort by mtime, descending. + return a.second > b.second; +} + +// Return true if |path| matches a known (file size, sha1sum) pair. +// The use of the file size is an optimization so we don't have to read in +// the entire file unless we have to. +bool IsBlacklistedBySha1sum(const FilePath& path) { + const struct BadEntry { + int64 size; + std::string sha1; + } bad_entries[] = { + // Flash 9 r31 - http://crbug.com/29237 + { 7040080, "fa5803061125ca47846713b34a26a42f1f1e98bb" }, + // Flash 9 r48 - http://crbug.com/29237 + { 7040036, "0c4b3768a6d4bfba003088e4b9090d381de1af2b" }, + }; + + int64 size; + if (!file_util::GetFileSize(path, &size)) + return false; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(bad_entries); i++) { + if (bad_entries[i].size != size) + continue; + + std::string file_content; + if (!file_util::ReadFileToString(path, &file_content)) + continue; + std::string sha1 = base::SHA1HashString(file_content); + std::string sha1_readable; + for (size_t j = 0; j < sha1.size(); j++) + StringAppendF(&sha1_readable, "%02x", sha1[j] & 0xFF); + if (bad_entries[i].sha1 == sha1_readable) + return true; + } + return false; +} + +// Some plugins are shells around other plugins; we prefer to use the +// real plugin directly, if it's available. This function returns +// true if we should prefer other plugins over this one. We'll still +// use a "undesirable" plugin if no other option is available. +bool IsUndesirablePlugin(const WebPluginInfo& info) { + std::string filename = info.path.BaseName().value(); + const char* kUndesiredPlugins[] = { + "npcxoffice", // Crossover + "npwrapper", // nspluginwrapper + }; + for (size_t i = 0; i < arraysize(kUndesiredPlugins); i++) { + if (filename.find(kUndesiredPlugins[i]) != std::string::npos) { + return true; + } + } + return false; +} + +// Return true if we shouldn't load a plugin at all. +// This is an ugly hack to blacklist Adobe Acrobat due to not supporting +// its Xt-based mainloop. +// http://code.google.com/p/chromium/issues/detail?id=38229 +// The gecko-mediaplayer plugins also crashes the entire browser sometimes. +// http://code.google.com/p/chromium/issues/detail?id=24507 +bool IsBlacklistedPlugin(const FilePath& path) { + const char* kBlackListedPlugins[] = { + "nppdf.so", // Adobe PDF + "gecko-mediaplayer", // Gecko Media Player + }; + std::string filename = path.BaseName().value(); + for (size_t i = 0; i < arraysize(kBlackListedPlugins); i++) { + if (filename.find(kBlackListedPlugins[i]) != std::string::npos) { + return true; + } + } + return IsBlacklistedBySha1sum(path); +} + +} // anonymous namespace + +namespace NPAPI { + +void PluginList::PlatformInit() { +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // See http://groups.google.com/group/chromium-dev/browse_thread/thread/7a70e5fcbac786a9 + // for discussion. + // We first consult Chrome-specific dirs, then fall back on the logic + // Mozilla uses. + + // Note: "extra" plugin dirs, including the Plugins subdirectory of + // your Chrome config, are examined before these. See the logic + // related to extra_plugin_dirs in plugin_list.cc. + + // The Chrome binary dir + "plugins/". + FilePath dir; + PathService::Get(base::DIR_EXE, &dir); + plugin_dirs->push_back(dir.Append("plugins")); + + // Mozilla code to reference: + // http://mxr.mozilla.org/firefox/ident?i=NS_APP_PLUGINS_DIR_LIST + // and tens of accompanying files (mxr is very helpful). + // This code carefully matches their behavior for compat reasons. + + // 1) MOZ_PLUGIN_PATH env variable. + const char* moz_plugin_path = getenv("MOZ_PLUGIN_PATH"); + if (moz_plugin_path) { + std::vector<std::string> paths; + SplitString(moz_plugin_path, ':', &paths); + for (size_t i = 0; i < paths.size(); ++i) + plugin_dirs->push_back(FilePath(paths[i])); + } + + // 2) NS_USER_PLUGINS_DIR: ~/.mozilla/plugins. + // This is a de-facto standard, so even though we're not Mozilla, let's + // look in there too. + FilePath home = file_util::GetHomeDir(); + if (!home.empty()) + plugin_dirs->push_back(home.Append(".mozilla/plugins")); + + // 3) NS_SYSTEM_PLUGINS_DIR: + // This varies across different browsers and versions, so check 'em all. + plugin_dirs->push_back(FilePath("/usr/lib/browser-plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/mozilla/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/firefox/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib/xulrunner-addons/plugins")); + +#if defined(ARCH_CPU_64_BITS) + // On my Ubuntu system, /usr/lib64 is a symlink to /usr/lib. + // But a user reported on their Fedora system they are separate. + plugin_dirs->push_back(FilePath("/usr/lib64/browser-plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/mozilla/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/firefox/plugins")); + plugin_dirs->push_back(FilePath("/usr/lib64/xulrunner-addons/plugins")); +#endif +} + +void PluginList::LoadPluginsFromDir(const FilePath& dir_path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + // See ScanPluginsDirectory near + // http://mxr.mozilla.org/firefox/source/modules/plugin/base/src/nsPluginHostImpl.cpp#5052 + + // Construct and stat a list of all filenames under consideration, for + // later sorting by mtime. + FileTimeList files; + file_util::FileEnumerator enumerator(dir_path, + false, // not recursive + file_util::FileEnumerator::FILES); + for (FilePath path = enumerator.Next(); !path.value().empty(); + path = enumerator.Next()) { + // Skip over Mozilla .xpt files. + if (path.MatchesExtension(FILE_PATH_LITERAL(".xpt"))) + continue; + + // Java doesn't like being loaded through a symlink, since it uses + // its path to find dependent data files. + // file_util::AbsolutePath calls through to realpath(), which resolves + // symlinks. + FilePath orig_path = path; + file_util::AbsolutePath(&path); + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Resolved " << orig_path.value() << " -> " << path.value(); + + if (visited_plugins->find(path) != visited_plugins->end()) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Skipping duplicate instance of " << path.value(); + continue; + } + visited_plugins->insert(path); + + if (IsBlacklistedPlugin(path)) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Skipping blacklisted plugin " << path.value(); + continue; + } + + // Flash stops working if the containing directory involves 'netscape'. + // No joke. So use the other path if it's better. + static const char kFlashPlayerFilename[] = "libflashplayer.so"; + static const char kNetscapeInPath[] = "/netscape/"; + if (path.BaseName().value() == kFlashPlayerFilename && + path.value().find(kNetscapeInPath) != std::string::npos) { + if (orig_path.value().find(kNetscapeInPath) == std::string::npos) { + // Go back to the old path. + path = orig_path; + } else { + LOG(ERROR) << "Flash misbehaves when used from a directory containing " + << kNetscapeInPath << ", so skipping " << orig_path.value(); + continue; + } + } + + // Get mtime. + file_util::FileInfo info; + if (!file_util::GetFileInfo(path, &info)) + continue; + + files.push_back(std::make_pair(path, info.last_modified)); + } + + // Sort the file list by time (and filename). + std::sort(files.begin(), files.end(), CompareTime); + + // Load the files in order. + for (FileTimeList::const_iterator i = files.begin(); i != files.end(); ++i) { + LoadPlugin(i->first, plugins); + } +} + + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Considering " << info.path.value() << " (" << info.name << ")"; + + if (IsUndesirablePlugin(info)) { + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << info.path.value() << " is undesirable."; + + // See if we have a better version of this plugin. + for (size_t i = 0; i < plugins->size(); ++i) { + if (plugins->at(i).name == info.name && + !IsUndesirablePlugin(plugins->at(i))) { + // Skip the current undesirable one so we can use the better one + // we just found. + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Skipping " << info.path.value() << ", preferring " + << plugins->at(i).path.value(); + return false; + } + } + } + + // TODO(evanm): prefer the newest version of flash, etc. here? + + LOG_IF(INFO, PluginList::DebugPluginLoading()) + << "Using " << info.path.value(); + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_list_win.cc b/webkit/glue/plugins/plugin_list_win.cc new file mode 100644 index 0000000..1c91916 --- /dev/null +++ b/webkit/glue/plugins/plugin_list_win.cc @@ -0,0 +1,404 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/plugin_list.h" + +#include <tchar.h> + +#include <set> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/registry.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +const TCHAR kRegistryApps[] = + _T("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths"); +const TCHAR kRegistryFirefox[] = _T("firefox.exe"); +const TCHAR kRegistryAcrobat[] = _T("Acrobat.exe"); +const TCHAR kRegistryAcrobatReader[] = _T("AcroRd32.exe"); +const TCHAR kRegistryWindowsMedia[] = _T("wmplayer.exe"); +const TCHAR kRegistryQuickTime[] = _T("QuickTimePlayer.exe"); +const TCHAR kRegistryPath[] = _T("Path"); +const TCHAR kRegistryFirefoxInstalled[] = + _T("SOFTWARE\\Mozilla\\Mozilla Firefox"); +const TCHAR kRegistryJava[] = + _T("Software\\JavaSoft\\Java Runtime Environment"); +const TCHAR kRegistryBrowserJavaVersion[] = _T("BrowserJavaVersion"); +const TCHAR kRegistryCurrentJavaVersion[] = _T("CurrentVersion"); +const TCHAR kRegistryJavaHome[] = _T("JavaHome"); +const TCHAR kJavaDeploy1[] = _T("npdeploytk.dll"); +const TCHAR kJavaDeploy2[] = _T("npdeployjava1.dll"); + +// The application path where we expect to find plugins. +void GetAppDirectory(std::set<FilePath>* plugin_dirs) { + FilePath app_path; + if (!webkit_glue::GetApplicationDirectory(&app_path)) + return; + + app_path = app_path.AppendASCII("plugins"); + plugin_dirs->insert(app_path); +} + +// The executable path where we expect to find plugins. +void GetExeDirectory(std::set<FilePath>* plugin_dirs) { + FilePath exe_path; + if (!webkit_glue::GetExeDirectory(&exe_path)) + return; + + exe_path = exe_path.AppendASCII("plugins"); + plugin_dirs->insert(exe_path); +} + +// Gets the installed path for a registered app. +bool GetInstalledPath(const TCHAR* app, FilePath* out) { + std::wstring reg_path(kRegistryApps); + reg_path.append(L"\\"); + reg_path.append(app); + + RegKey key(HKEY_LOCAL_MACHINE, reg_path.c_str()); + std::wstring path; + if (key.ReadValue(kRegistryPath, &path)) { + *out = FilePath(path); + return true; + } + + return false; +} + +// Search the registry at the given path and detect plugin directories. +void GetPluginsInRegistryDirectory( + HKEY root_key, + const std::wstring& registry_folder, + std::set<FilePath>* plugin_dirs) { + for (RegistryKeyIterator iter(root_key, registry_folder.c_str()); + iter.Valid(); ++iter) { + // Use the registry to gather plugin across the file system. + std::wstring reg_path = registry_folder; + reg_path.append(L"\\"); + reg_path.append(iter.Name()); + RegKey key(root_key, reg_path.c_str()); + + std::wstring path; + if (key.ReadValue(kRegistryPath, &path)) + plugin_dirs->insert(FilePath(path)); + } +} + +// Enumerate through the registry key to find all installed FireFox paths. +// FireFox 3 beta and version 2 can coexist. See bug: 1025003 +void GetFirefoxInstalledPaths(std::vector<FilePath>* out) { + RegistryKeyIterator it(HKEY_LOCAL_MACHINE, kRegistryFirefoxInstalled); + for (; it.Valid(); ++it) { + std::wstring full_path = std::wstring(kRegistryFirefoxInstalled) + L"\\" + + it.Name() + L"\\Main"; + RegKey key(HKEY_LOCAL_MACHINE, full_path.c_str(), KEY_READ); + std::wstring install_dir; + if (!key.ReadValue(L"Install Directory", &install_dir)) + continue; + out->push_back(FilePath(install_dir)); + } +} + +// Get plugin directory locations from the Firefox install path. This is kind +// of a kludge, but it helps us locate the flash player for users that +// already have it for firefox. Not having to download yet-another-plugin +// is a good thing. +void GetFirefoxDirectory(std::set<FilePath>* plugin_dirs) { + std::vector<FilePath> paths; + GetFirefoxInstalledPaths(&paths); + for (unsigned int i = 0; i < paths.size(); ++i) { + plugin_dirs->insert(paths[i].Append(L"plugins")); + } + + FilePath firefox_app_data_plugin_path; + if (PathService::Get(base::DIR_APP_DATA, &firefox_app_data_plugin_path)) { + firefox_app_data_plugin_path = + firefox_app_data_plugin_path.AppendASCII("Mozilla") + .AppendASCII("plugins"); + plugin_dirs->insert(firefox_app_data_plugin_path); + } +} + +// Hardcoded logic to detect Acrobat plugins locations. +void GetAcrobatDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (!GetInstalledPath(kRegistryAcrobatReader, &path) && + !GetInstalledPath(kRegistryAcrobat, &path)) { + return; + } + + plugin_dirs->insert(path.Append(L"Browser")); +} + +// Hardcoded logic to detect QuickTime plugin location. +void GetQuicktimeDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (GetInstalledPath(kRegistryQuickTime, &path)) + plugin_dirs->insert(path.Append(L"plugins")); +} + +// Hardcoded logic to detect Windows Media Player plugin location. +void GetWindowsMediaDirectory(std::set<FilePath>* plugin_dirs) { + FilePath path; + if (GetInstalledPath(kRegistryWindowsMedia, &path)) + plugin_dirs->insert(path); + + // If the Windows Media Player Firefox plugin is installed before Firefox, + // the plugin will get written under PFiles\Plugins on one the drives + // (usually, but not always, the last letter). + int size = GetLogicalDriveStrings(0, NULL); + if (size) { + scoped_array<wchar_t> strings(new wchar_t[size]); + if (GetLogicalDriveStrings(size, strings.get())) { + wchar_t* next_drive = strings.get(); + while (*next_drive) { + if (GetDriveType(next_drive) == DRIVE_FIXED) { + FilePath pfiles(next_drive); + pfiles = pfiles.Append(L"PFiles\\Plugins"); + if (file_util::PathExists(pfiles)) + plugin_dirs->insert(pfiles); + } + next_drive = &next_drive[wcslen(next_drive) + 1]; + } + } + } +} + +// Hardcoded logic to detect Java plugin location. +void GetJavaDirectory(std::set<FilePath>* plugin_dirs) { + // Load the new NPAPI Java plugin + // 1. Open the main JRE key under HKLM + RegKey java_key(HKEY_LOCAL_MACHINE, kRegistryJava, KEY_QUERY_VALUE); + + // 2. Read the current Java version + std::wstring java_version; + if (!java_key.ReadValue(kRegistryBrowserJavaVersion, &java_version)) + java_key.ReadValue(kRegistryCurrentJavaVersion, &java_version); + + if (!java_version.empty()) { + java_key.OpenKey(java_version.c_str(), KEY_QUERY_VALUE); + + // 3. Install path of the JRE binaries is specified in "JavaHome" + // value under the Java version key. + std::wstring java_plugin_directory; + if (java_key.ReadValue(kRegistryJavaHome, &java_plugin_directory)) { + // 4. The new plugin resides under the 'bin/new_plugin' + // subdirectory. + DCHECK(!java_plugin_directory.empty()); + java_plugin_directory.append(L"\\bin\\new_plugin"); + + // 5. We don't know the exact name of the DLL but it's in the form + // NP*.dll so just invoke LoadPlugins on this path. + plugin_dirs->insert(FilePath(java_plugin_directory)); + } + } +} + +} // anonymous namespace + +namespace NPAPI { + +void PluginList::PlatformInit() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + dont_load_new_wmp_ = command_line.HasSwitch(kUseOldWMPPluginSwitch); +} + +void PluginList::GetPluginDirectories(std::vector<FilePath>* plugin_dirs) { + // We use a set for uniqueness, which we require, over order, which we do not. + std::set<FilePath> dirs; + + // Load from the application-specific area + GetAppDirectory(&dirs); + + // Load from the executable area + GetExeDirectory(&dirs); + + // Load Java + GetJavaDirectory(&dirs); + + // Load firefox plugins too. This is mainly to try to locate + // a pre-installed Flash player. + GetFirefoxDirectory(&dirs); + + // Firefox hard-codes the paths of some popular plugins to ensure that + // the plugins are found. We are going to copy this as well. + GetAcrobatDirectory(&dirs); + GetQuicktimeDirectory(&dirs); + GetWindowsMediaDirectory(&dirs); + + for (std::set<FilePath>::iterator i = dirs.begin(); i != dirs.end(); ++i) + plugin_dirs->push_back(*i); +} + +void PluginList::LoadPluginsFromDir(const FilePath &path, + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + WIN32_FIND_DATA find_file_data; + HANDLE find_handle; + + std::wstring dir = path.value(); + // FindFirstFile requires that you specify a wildcard for directories. + dir.append(L"\\NP*.DLL"); + + find_handle = FindFirstFile(dir.c_str(), &find_file_data); + if (find_handle == INVALID_HANDLE_VALUE) + return; + + do { + if (!(find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + FilePath filename = path.Append(find_file_data.cFileName); + LoadPlugin(filename, plugins); + visited_plugins->insert(filename); + } + } while (FindNextFile(find_handle, &find_file_data) != 0); + + DCHECK(GetLastError() == ERROR_NO_MORE_FILES); + FindClose(find_handle); +} + +void PluginList::LoadPluginsFromRegistry( + std::vector<WebPluginInfo>* plugins, + std::set<FilePath>* visited_plugins) { + std::set<FilePath> plugin_dirs; + + GetPluginsInRegistryDirectory( + HKEY_CURRENT_USER, kRegistryMozillaPlugins, &plugin_dirs); + GetPluginsInRegistryDirectory( + HKEY_LOCAL_MACHINE, kRegistryMozillaPlugins, &plugin_dirs); + + for (std::set<FilePath>::iterator i = plugin_dirs.begin(); + i != plugin_dirs.end(); ++i) { + LoadPlugin(*i, plugins); + visited_plugins->insert(*i); + } +} + +// Returns true if the given plugins share at least one mime type. This is used +// to differentiate newer versions of a plugin vs two plugins which happen to +// have the same filename. +bool HaveSharedMimeType(const WebPluginInfo& plugin1, + const WebPluginInfo& plugin2) { + for (size_t i = 0; i < plugin1.mime_types.size(); ++i) { + for (size_t j = 0; j < plugin2.mime_types.size(); ++j) { + if (plugin1.mime_types[i].mime_type == plugin2.mime_types[j].mime_type) + return true; + } + } + + return false; +} + +// Compares Windows style version strings (i.e. 1,2,3,4). Returns true if b's +// version is newer than a's, or false if it's equal or older. +bool IsNewerVersion(const std::wstring& a, const std::wstring& b) { + std::vector<std::wstring> a_ver, b_ver; + SplitString(a, ',', &a_ver); + SplitString(b, ',', &b_ver); + if (a_ver.size() == 1 && b_ver.size() == 1) { + a_ver.clear(); + b_ver.clear(); + SplitString(a, '.', &a_ver); + SplitString(b, '.', &b_ver); + } + if (a_ver.size() != b_ver.size()) + return false; + for (size_t i = 0; i < a_ver.size(); i++) { + int cur_a = StringToInt(a_ver[i]); + int cur_b = StringToInt(b_ver[i]); + if (cur_a > cur_b) + return false; + if (cur_a < cur_b) + return true; + } + return false; +} + +bool PluginList::ShouldLoadPlugin(const WebPluginInfo& info, + std::vector<WebPluginInfo>* plugins) { + // Version check + + for (size_t i = 0; i < plugins->size(); ++i) { + std::wstring plugin1 = + StringToLowerASCII((*plugins)[i].path.BaseName().ToWStringHack()); + std::wstring plugin2 = + StringToLowerASCII(info.path.BaseName().ToWStringHack()); + if ((plugin1 == plugin2 && HaveSharedMimeType((*plugins)[i], info)) || + (plugin1 == kJavaDeploy1 && plugin2 == kJavaDeploy2) || + (plugin1 == kJavaDeploy2 && plugin2 == kJavaDeploy1)) { + if (!IsNewerVersion((*plugins)[i].version, info.version)) + return false; // We have loaded a plugin whose version is newer. + + plugins->erase(plugins->begin() + i); + break; + } + } + + // Troublemakers + + std::wstring filename = StringToLowerASCII(info.path.BaseName().value()); + // Depends on XPCOM. + if (filename == kMozillaActiveXPlugin) + return false; + + // Disable the Yahoo Application State plugin as it crashes the plugin + // process on return from NPObjectStub::OnInvoke. Please refer to + // http://b/issue?id=1372124 for more information. + if (filename == kYahooApplicationStatePlugin) + return false; + + // Disable the WangWang protocol handler plugin (npww.dll) as it crashes + // chrome during shutdown. Firefox also disables this plugin. + // Please refer to http://code.google.com/p/chromium/issues/detail?id=3953 + // for more information. + if (filename == kWanWangProtocolHandlerPlugin) + return false; + + // We only work with newer versions of the Java plugin which use NPAPI only + // and don't depend on XPCOM. + if (filename == kJavaPlugin1 || filename == kJavaPlugin2) { + std::vector<std::wstring> ver; + SplitString(info.version, '.', &ver); + int major, minor, update; + if (ver.size() == 4 && + StringToInt(ver[0], &major) && + StringToInt(ver[1], &minor) && + StringToInt(ver[2], &update)) { + if (major == 6 && minor == 0 && update < 120) + return false; // Java SE6 Update 11 or older. + } + } + + // Special WMP handling + + // If both the new and old WMP plugins exist, only load the new one. + if (filename == kNewWMPPlugin) { + if (dont_load_new_wmp_) + return false; + + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName().value() == kOldWMPPlugin) { + plugins->erase(plugins->begin() + i); + break; + } + } + } else if (filename == kOldWMPPlugin) { + for (size_t i = 0; i < plugins->size(); ++i) { + if ((*plugins)[i].path.BaseName().value() == kNewWMPPlugin) + return false; + } + } + + return true; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream.cc b/webkit/glue/plugins/plugin_stream.cc new file mode 100644 index 0000000..728b180 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream.cc @@ -0,0 +1,254 @@ +// Copyright (c) 2006-2008 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. + +// TODO : Support NP_ASFILEONLY mode +// TODO : Support NP_SEEK mode +// TODO : Support SEEKABLE=true in NewStream + +#include "webkit/glue/plugins/plugin_stream.h" + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "net/base/mime_util.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +PluginStream::~PluginStream() { + // always close our temporary files. + CloseTempFile(); + free(const_cast<char*>(stream_.url)); +} + +bool PluginStream::Open(const std::string &mime_type, + const std::string &headers, + uint32 length, + uint32 last_modified, + bool request_is_seekable) { + headers_ = headers; + NPP id = instance_->npp(); + stream_.end = length; + stream_.lastmodified = last_modified; + stream_.pdata = 0; + stream_.ndata = id->ndata; + stream_.notifyData = notify_data_; + if (!headers_.empty()) + stream_.headers = headers_.c_str(); + + bool seekable_stream = false; + if (request_is_seekable) { + std::string headers_lc = StringToLowerASCII(headers); + if (headers_lc.find("accept-ranges: bytes") != std::string::npos) { + seekable_stream = true; + } + } + + const char *char_mime_type = "application/x-unknown-content-type"; + std::string temp_mime_type; + if (!mime_type.empty()) { + char_mime_type = mime_type.c_str(); + } else { + GURL gurl(stream_.url); + +#if defined(OS_WIN) + FilePath path(UTF8ToWide(gurl.path())); +#elif defined(OS_POSIX) + FilePath path(gurl.path()); +#endif + if (net::GetMimeTypeFromFile(path, &temp_mime_type)) + char_mime_type = temp_mime_type.c_str(); + } + + // Silverlight expects a valid mime type + DCHECK(strlen(char_mime_type) != 0); + NPError err = instance_->NPP_NewStream((NPMIMEType)char_mime_type, + &stream_, seekable_stream, + &requested_plugin_mode_); + if (err != NPERR_NO_ERROR) { + Notify(err); + return false; + } + + opened_ = true; + + if (requested_plugin_mode_ == NP_SEEK) { + seekable_stream_ = true; + } + // If the plugin has requested certain modes, then we need a copy + // of this file on disk. Open it and save it as we go. + if (requested_plugin_mode_ == NP_ASFILEONLY || + requested_plugin_mode_ == NP_ASFILE) { + if (OpenTempFile() == false) + return false; + } + + mime_type_ = char_mime_type; + return true; +} + +int PluginStream::Write(const char *buffer, const int length, + int data_offset) { + // There may be two streams to write to - the plugin and the file. + // It is unclear what to do if we cannot write to both. The rules of + // this function are that the plugin must consume at least as many + // bytes as returned by the WriteReady call. So, we will attempt to + // write that many to both streams. If we can't write that many bytes + // to each stream, we'll return failure. + + DCHECK(opened_); + if (WriteToFile(buffer, length) && + WriteToPlugin(buffer, length, data_offset)) + return length; + + return -1; +} + +bool PluginStream::WriteToFile(const char *buf, size_t length) { + // For ASFILEONLY, ASFILE, and SEEK modes, we need to write + // to the disk + if (TempFileIsValid() && + (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) ) { + size_t totalBytesWritten = 0, bytes; + do { + bytes = WriteBytes(buf, length); + totalBytesWritten += bytes; + } while (bytes > 0U && totalBytesWritten < length); + + if (totalBytesWritten != length) + return false; + } + + return true; +} + +bool PluginStream::WriteToPlugin(const char *buf, const int length, + const int data_offset) { + // For NORMAL and ASFILE modes, we send the data to the plugin now + if (requested_plugin_mode_ != NP_NORMAL && + requested_plugin_mode_ != NP_ASFILE && + requested_plugin_mode_ != NP_SEEK) + return true; + + int written = TryWriteToPlugin(buf, length, data_offset); + if (written == -1) + return false; + + if (written < length) { + // Buffer the remaining data. + size_t remaining = length - written; + size_t previous_size = delivery_data_.size(); + delivery_data_.resize(previous_size + remaining); + data_offset_ = data_offset; + memcpy(&delivery_data_[previous_size], buf + written, remaining); + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &PluginStream::OnDelayDelivery)); + } + + return true; +} + +void PluginStream::OnDelayDelivery() { + // It is possible that the plugin stream may have closed before the task + // was hit. + if (!opened_) { + return; + } + + int size = static_cast<int>(delivery_data_.size()); + int written = TryWriteToPlugin(&delivery_data_.front(), size, + data_offset_); + if (written > 0) { + // Remove the data that we already wrote. + delivery_data_.erase(delivery_data_.begin(), + delivery_data_.begin() + written); + } +} + +int PluginStream::TryWriteToPlugin(const char *buf, const int length, + const int data_offset) { + int byte_offset = 0; + + if (data_offset > 0) + data_offset_ = data_offset; + + while (byte_offset < length) { + int bytes_remaining = length - byte_offset; + int bytes_to_write = instance_->NPP_WriteReady(&stream_); + if (bytes_to_write > bytes_remaining) + bytes_to_write = bytes_remaining; + + if (bytes_to_write == 0) + return byte_offset; + + int bytes_consumed = instance_->NPP_Write( + &stream_, data_offset_, bytes_to_write, + const_cast<char*>(buf + byte_offset)); + if (bytes_consumed < 0) { + // The plugin failed, which means that we need to close the stream. + Close(NPRES_NETWORK_ERR); + return -1; + } + if (bytes_consumed == 0) { + // The plugin couldn't take all of the data now. + return byte_offset; + } + + // The plugin might report more that we gave it. + bytes_consumed = std::min(bytes_consumed, bytes_to_write); + + data_offset_ += bytes_consumed; + byte_offset += bytes_consumed; + } + + if (close_on_write_data_) + Close(NPRES_DONE); + + return length; +} + +bool PluginStream::Close(NPReason reason) { + if (opened_ == true) { + opened_ = false; + + if (delivery_data_.size()) { + if (reason == NPRES_DONE) { + // There is more data to be streamed, don't destroy the stream now. + close_on_write_data_ = true; + return true; + } else { + // Stop any pending data from being streamed + delivery_data_.resize(0); + } + } + + // If we have a temp file, be sure to close it. + // Also, allow the plugin to access it now. + if (TempFileIsValid()) { + CloseTempFile(); + if (reason == NPRES_DONE) + WriteAsFile(); + } + + if (stream_.ndata != NULL) { + // Stream hasn't been closed yet. + NPError err = instance_->NPP_DestroyStream(&stream_, reason); + DCHECK(err == NPERR_NO_ERROR); + } + } + + Notify(reason); + return true; +} + +void PluginStream::Notify(NPReason reason) { + if (notify_needed_) { + instance_->NPP_URLNotify(stream_.url, reason, notify_data_); + notify_needed_ = false; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream.h b/webkit/glue/plugins/plugin_stream.h new file mode 100644 index 0000000..f15ea66 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream.h @@ -0,0 +1,146 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_H__ + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "third_party/npapi/bindings/npapi.h" + +namespace webkit_glue { +class WebPluginResourceClient; +} + +namespace NPAPI { + +class PluginInstance; + +// Base class for a NPAPI stream. Tracks basic elements +// of a stream for NPAPI notifications and stream position. +class PluginStream : public base::RefCounted<PluginStream> { + public: + // Create a new PluginStream object. If needNotify is true, then the + // plugin will be notified when the stream has been fully sent. + PluginStream(PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data); + + // In case of a redirect, this can be called to update the url. But it must + // be called before Open(). + void UpdateUrl(const char* url); + + // Opens the stream to the Plugin. + // If the mime-type is not specified, we'll try to find one based on the + // mime-types table and the extension (if any) in the URL. + // If the size of the stream is known, use length to set the size. If + // not known, set length to 0. + // The request_is_seekable parameter indicates whether byte range requests + // can be issued on the stream. + bool Open(const std::string &mime_type, + const std::string &headers, + uint32 length, + uint32 last_modified, + bool request_is_seekable); + + // Writes to the stream. + int Write(const char *buf, const int len, int data_offset); + + // Write the result as a file. + void WriteAsFile(); + + // Notify the plugin that a stream is complete. + void Notify(NPReason reason); + + // Close the stream. + virtual bool Close(NPReason reason); + + virtual webkit_glue::WebPluginResourceClient* AsResourceClient() { + return NULL; + } + + // Cancels any HTTP requests initiated by the stream. + virtual void CancelRequest() {} + + const NPStream* stream() const { return &stream_; } + + // setter/getter for the seekable attribute on the stream. + bool seekable() const { return seekable_stream_; } + + void set_seekable(bool seekable) { seekable_stream_ = seekable; } + + // getters for reading the notification related attributes on the stream. + bool notify_needed() const { return notify_needed_; } + + void* notify_data() const { return notify_data_; } + + protected: + friend class base::RefCounted<PluginStream>; + + virtual ~PluginStream(); + + PluginInstance* instance() { return instance_.get(); } + // Check if the stream is open. + bool open() { return opened_; } + + private: + + // Open a temporary file for this stream. + // If successful, will set temp_file_name_, temp_file_handle_, and + // return true. + bool OpenTempFile(); + + // Closes the temporary file if it is open. + void CloseTempFile(); + + // Sends the data to the file. Called From WriteToFile. + size_t WriteBytes(const char *buf, size_t length); + + // Sends the data to the file if it's open. + bool WriteToFile(const char *buf, size_t length); + + // Sends the data to the plugin. If it's not ready, handles buffering it + // and retrying later. + bool WriteToPlugin(const char *buf, const int length, const int data_offset); + + // Send the data to the plugin, returning how many bytes it accepted, or -1 + // if an error occurred. + int TryWriteToPlugin(const char *buf, const int length, const int data_offset); + + // The callback which calls TryWriteToPlugin. + void OnDelayDelivery(); + + // Returns true if the temp file is valid and open for writing. + bool TempFileIsValid(); + + private: + NPStream stream_; + std::string headers_; + scoped_refptr<PluginInstance> instance_; + bool notify_needed_; + void * notify_data_; + bool close_on_write_data_; + uint16 requested_plugin_mode_; + bool opened_; +#if defined(OS_WIN) + char temp_file_name_[MAX_PATH]; + HANDLE temp_file_handle_; +#elif defined(OS_POSIX) + FILE* temp_file_; + FilePath temp_file_path_; +#endif + std::vector<char> delivery_data_; + int data_offset_; + bool seekable_stream_; + std::string mime_type_; + DISALLOW_COPY_AND_ASSIGN(PluginStream); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_H__ diff --git a/webkit/glue/plugins/plugin_stream_posix.cc b/webkit/glue/plugins/plugin_stream_posix.cc new file mode 100644 index 0000000..d0e2291 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_posix.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/plugin_stream.h" + +#include <string.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +namespace NPAPI { + +PluginStream::PluginStream( + PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data) + : instance_(instance), + notify_needed_(need_notify), + notify_data_(notify_data), + close_on_write_data_(false), + requested_plugin_mode_(NP_NORMAL), + opened_(false), + temp_file_(NULL), + temp_file_path_(), + data_offset_(0), + seekable_stream_(false) { + memset(&stream_, 0, sizeof(stream_)); + stream_.url = strdup(url); +} + +void PluginStream::UpdateUrl(const char* url) { + DCHECK(!opened_); + free(const_cast<char*>(stream_.url)); + stream_.url = strdup(url); +} + +void PluginStream::WriteAsFile() { + if (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) + instance_->NPP_StreamAsFile(&stream_, temp_file_path_.value().c_str()); +} + +size_t PluginStream::WriteBytes(const char *buf, size_t length) { + return fwrite(buf, sizeof(char), length, temp_file_); +} + +bool PluginStream::OpenTempFile() { + DCHECK(temp_file_ == NULL); + + if (file_util::CreateTemporaryFile(&temp_file_path_)) + temp_file_ = file_util::OpenFile(temp_file_path_, "a"); + + if (!temp_file_) { + temp_file_path_ = FilePath(""); + return false; + } + + return true; +} + +void PluginStream::CloseTempFile() { + file_util::CloseFile(temp_file_); + temp_file_ = NULL; +} + +bool PluginStream::TempFileIsValid() { + return temp_file_ != NULL; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream_url.cc b/webkit/glue/plugins/plugin_stream_url.cc new file mode 100644 index 0000000..6694139 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_url.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_stream_url.h" + +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugin.h" + +namespace NPAPI { + +PluginStreamUrl::PluginStreamUrl( + unsigned long resource_id, + const GURL &url, + PluginInstance *instance, + bool notify_needed, + void *notify_data) + : PluginStream(instance, url.spec().c_str(), notify_needed, notify_data), + url_(url), + id_(resource_id) { +} + +PluginStreamUrl::~PluginStreamUrl() { + if (instance() && instance()->webplugin()) { + instance()->webplugin()->ResourceClientDeleted(AsResourceClient()); + } +} + +bool PluginStreamUrl::Close(NPReason reason) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the destroy stream handler. + scoped_refptr<PluginStream> protect(this); + CancelRequest(); + bool result = PluginStream::Close(reason); + instance()->RemoveStream(this); + return result; +} + +void PluginStreamUrl::WillSendRequest(const GURL& url) { + url_ = url; + UpdateUrl(url.spec().c_str()); +} + +void PluginStreamUrl::DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the new stream handler. + scoped_refptr<PluginStream> protect(this); + + bool opened = Open(mime_type, + headers, + expected_length, + last_modified, + request_is_seekable); + if (!opened) { + CancelRequest(); + instance()->RemoveStream(this); + } else { + if (id_ > 0) + instance()->webplugin()->SetDeferResourceLoading(id_, false); + } +} + +void PluginStreamUrl::DidReceiveData(const char* buffer, int length, + int data_offset) { + if (!open()) + return; + + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the write handlers + scoped_refptr<PluginStream> protect(this); + + if (length > 0) { + // The PluginStreamUrl instance could get deleted if the plugin fails to + // accept data in NPP_Write. + if (Write(const_cast<char*>(buffer), length, data_offset) > 0) { + if (id_ > 0) + instance()->webplugin()->SetDeferResourceLoading(id_, false); + } + } +} + +void PluginStreamUrl::DidFinishLoading() { + if (!seekable()) { + Close(NPRES_DONE); + } +} + +void PluginStreamUrl::DidFail() { + Close(NPRES_NETWORK_ERR); +} + +void PluginStreamUrl::CancelRequest() { + if (id_ > 0) { + if (instance()->webplugin()) { + instance()->webplugin()->CancelResource(id_); + } + id_ = 0; + } +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_stream_url.h b/webkit/glue/plugins/plugin_stream_url.h new file mode 100644 index 0000000..01a6da9 --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_url.h @@ -0,0 +1,69 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ + + +#include "webkit/glue/plugins/plugin_stream.h" +#include "webkit/glue/plugins/webplugin.h" +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +class PluginInstance; + +// A NPAPI Stream based on a URL. +class PluginStreamUrl : public PluginStream, + public webkit_glue::WebPluginResourceClient { + public: + // Create a new stream for sending to the plugin by fetching + // a URL. If notifyNeeded is set, then the plugin will be notified + // when the stream has been fully sent to the plugin. Initialize + // must be called before the object is used. + PluginStreamUrl(unsigned long resource_id, + const GURL &url, + PluginInstance *instance, + bool notify_needed, + void *notify_data); + virtual ~PluginStreamUrl(); + + // Stop sending the stream to the client. + // Overrides the base Close so we can cancel our fetching the URL if + // it is still loading. + virtual bool Close(NPReason reason); + + virtual webkit_glue::WebPluginResourceClient* AsResourceClient() { + return static_cast<webkit_glue::WebPluginResourceClient*>(this); + } + + virtual void CancelRequest(); + + // + // WebPluginResourceClient methods + // + void WillSendRequest(const GURL& url); + void DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable); + void DidReceiveData(const char* buffer, int length, int data_offset); + void DidFinishLoading(); + void DidFail(); + bool IsMultiByteResponseExpected() { + return seekable(); + } + + + private: + GURL url_; + unsigned long id_; + + DISALLOW_COPY_AND_ASSIGN(PluginStreamUrl); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_STREAM_URL_H__ diff --git a/webkit/glue/plugins/plugin_stream_win.cc b/webkit/glue/plugins/plugin_stream_win.cc new file mode 100644 index 0000000..fffa6fe --- /dev/null +++ b/webkit/glue/plugins/plugin_stream_win.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/plugin_stream.h" + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" + +namespace NPAPI { + +PluginStream::PluginStream( + PluginInstance *instance, + const char *url, + bool need_notify, + void *notify_data) + : instance_(instance), + notify_needed_(need_notify), + notify_data_(notify_data), + close_on_write_data_(false), + opened_(false), + requested_plugin_mode_(NP_NORMAL), + temp_file_handle_(INVALID_HANDLE_VALUE), + seekable_stream_(false), + data_offset_(0) { + memset(&stream_, 0, sizeof(stream_)); + stream_.url = _strdup(url); + temp_file_name_[0] = '\0'; +} + +void PluginStream::UpdateUrl(const char* url) { + DCHECK(!opened_); + free(const_cast<char*>(stream_.url)); + stream_.url = _strdup(url); +} + +void PluginStream::WriteAsFile() { + if (requested_plugin_mode_ == NP_ASFILE || + requested_plugin_mode_ == NP_ASFILEONLY) + instance_->NPP_StreamAsFile(&stream_, temp_file_name_); +} + +size_t PluginStream::WriteBytes(const char *buf, size_t length) { + DWORD bytes; + + if (!WriteFile(temp_file_handle_, buf, length, &bytes, 0)) + return 0U; + + return static_cast<size_t>(bytes); +} + +bool PluginStream::OpenTempFile() { + DCHECK(temp_file_handle_ == INVALID_HANDLE_VALUE); + + // The reason for using all the Ascii versions of these filesystem + // calls is that the filename which we pass back to the plugin + // via NPAPI is an ascii filename. Otherwise, we'd use wide-chars. + // + // TODO: + // This is a bug in NPAPI itself, and it needs to be fixed. + // The case which will fail is if a user has a multibyte name, + // but has the system locale set to english. GetTempPathA will + // return junk in this case, causing us to be unable to open the + // file. + + char temp_directory[MAX_PATH]; + if (GetTempPathA(MAX_PATH, temp_directory) == 0) + return false; + if (GetTempFileNameA(temp_directory, "npstream", 0, temp_file_name_) == 0) + return false; + temp_file_handle_ = CreateFileA(temp_file_name_, + FILE_ALL_ACCESS, + FILE_SHARE_READ, + 0, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + 0); + if (temp_file_handle_ == INVALID_HANDLE_VALUE) { + temp_file_name_[0] = '\0'; + return false; + } + return true; +} + +void PluginStream::CloseTempFile() { + if (temp_file_handle_ != INVALID_HANDLE_VALUE) { + CloseHandle(temp_file_handle_); + temp_file_handle_ = INVALID_HANDLE_VALUE; + } +} + +bool PluginStream::TempFileIsValid() { + return temp_file_handle_ != INVALID_HANDLE_VALUE; +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_string_stream.cc b/webkit/glue/plugins/plugin_string_stream.cc new file mode 100644 index 0000000..f174267 --- /dev/null +++ b/webkit/glue/plugins/plugin_string_stream.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/plugin_string_stream.h" + +#include "googleurl/src/gurl.h" + +namespace NPAPI { + +PluginStringStream::PluginStringStream( + PluginInstance* instance, + const GURL& url, + bool notify_needed, + void* notify_data) + : PluginStream(instance, url.spec().c_str(), notify_needed, notify_data) { +} + +PluginStringStream::~PluginStringStream() { +} + +void PluginStringStream::SendToPlugin(const std::string &data, + const std::string &mime_type) { + // Protect the stream against it being destroyed or the whole plugin instance + // being destroyed within the plugin stream callbacks. + scoped_refptr<PluginStringStream> protect(this); + + int length = static_cast<int>(data.length()); + if (Open(mime_type, std::string(), length, 0, false)) { + // TODO - check if it was not fully sent, and figure out a backup plan. + int written = Write(data.c_str(), length, 0); + NPReason reason = written == length ? NPRES_DONE : NPRES_NETWORK_ERR; + Close(reason); + } +} + +} diff --git a/webkit/glue/plugins/plugin_string_stream.h b/webkit/glue/plugins/plugin_string_stream.h new file mode 100644 index 0000000..68db2bf --- /dev/null +++ b/webkit/glue/plugins/plugin_string_stream.h @@ -0,0 +1,39 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ + +#include "webkit/glue/plugins/plugin_stream.h" + +class GURL; + +namespace NPAPI { + +class PluginInstance; + +// An NPAPI stream from a string. +class PluginStringStream : public PluginStream { + public: + // Create a new stream for sending to the plugin. + // If notify_needed, will notify the plugin after the data has + // all been sent. + PluginStringStream(PluginInstance* instance, + const GURL& url, + bool notify_needed, + void* notify_data); + + // Initiates the sending of data to the plugin. + void SendToPlugin(const std::string& data, + const std::string& mime_type); + + private: + virtual ~PluginStringStream(); + + DISALLOW_COPY_AND_ASSIGN(PluginStringStream); +}; + +} // namespace NPAPI + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_STRING_STREAM_H_ diff --git a/webkit/glue/plugins/plugin_stubs.cc b/webkit/glue/plugins/plugin_stubs.cc new file mode 100644 index 0000000..f8210c3 --- /dev/null +++ b/webkit/glue/plugins/plugin_stubs.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 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. + +// This file stubs out some functions needed to make the linker happy +// without linking in all the plugin code. It should be removed once +// we have plugins working on all platforms. + +// TODO(port): remove this file. + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_stream.h" + +namespace NPAPI { + +PluginStream::~PluginStream() { + NOTIMPLEMENTED(); +} + +bool PluginStream::Close(NPReason reason) { + NOTIMPLEMENTED(); + return false; +} + +void PluginInstance::NPP_StreamAsFile(NPStream*, const char*) { + NOTIMPLEMENTED(); +} + +} // namespace NPAPI diff --git a/webkit/glue/plugins/plugin_switches.cc b/webkit/glue/plugins/plugin_switches.cc new file mode 100644 index 0000000..eb5c958 --- /dev/null +++ b/webkit/glue/plugins/plugin_switches.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/plugin_switches.h" + +namespace switches { + +// Enables the testing interface for PPAPI. +const char kEnablePepperTesting[] = "enable-pepper-testing"; + +// Dumps extra logging about plugin loading to the log file. +const char kDebugPluginLoading[] = "debug-plugin-loading"; + +} // namespace switches diff --git a/webkit/glue/plugins/plugin_switches.h b/webkit/glue/plugins/plugin_switches.h new file mode 100644 index 0000000..772c047 --- /dev/null +++ b/webkit/glue/plugins/plugin_switches.h @@ -0,0 +1,15 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PLUGIN_SWITCHES_H_ +#define WEBKIT_GLUE_PLUGINS_PLUGIN_SWITCHES_H_ + +namespace switches { + +extern const char kDebugPluginLoading[]; +extern const char kEnablePepperTesting[]; + +} // namespace switches + +#endif // WEBKIT_GLUE_PLUGINS_PLUGIN_SWITCHES_H_ diff --git a/webkit/glue/plugins/plugin_web_event_converter_mac.h b/webkit/glue/plugins/plugin_web_event_converter_mac.h new file mode 100644 index 0000000..02f113a --- /dev/null +++ b/webkit/glue/plugins/plugin_web_event_converter_mac.h @@ -0,0 +1,66 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ +#define WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ + +#include "third_party/npapi/bindings/npapi.h" + +namespace WebKit { +class WebInputEvent; +class WebKeyboardEvent; +class WebMouseEvent; +class WebMouseWheelEvent; +} + +// Utility class to translating WebInputEvent structs to equivalent structures +// suitable for sending to Mac plugins (via NPP_HandleEvent). +class PluginWebEventConverter { + public: + PluginWebEventConverter() {} + virtual ~PluginWebEventConverter() {} + + // Initializes a converter for the given web event. Returns false if the event + // could not be converted. + virtual bool InitWithEvent(const WebKit::WebInputEvent& web_event); + + // Sets a zoom level to apply to mouse coordinates. Must be called after + // InitWithEvent. + // TODO(stuartmorgan): Re-evaluate whether this is necessary when + // http://crbug.com/9996 is fixed. + virtual void SetZoomLevel(float zoom) = 0; + + // Returns a pointer to a plugin event--suitable for passing to + // NPP_HandleEvent--corresponding to the the web event this converter was + // created with. The pointer is valid only as long as this object is. + // Returns NULL iff InitWithEvent returned false. + virtual void* plugin_event() = 0; + +protected: + // To be overridden by subclasses to store a converted plugin representation + // of the given web event, suitable for returning from plugin_event. + // Returns true if the event was successfully converted. + virtual bool ConvertKeyboardEvent( + const WebKit::WebKeyboardEvent& web_event) = 0; + virtual bool ConvertMouseEvent(const WebKit::WebMouseEvent& web_event) = 0; + virtual bool ConvertMouseWheelEvent( + const WebKit::WebMouseWheelEvent& web_event) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(PluginWebEventConverter); +}; + +// Factory for generating PluginWebEventConverter objects by event model. +class PluginWebEventConverterFactory { + public: + // Returns a new PluginWebEventConverter corresponding to the given plugin + // event model. + static PluginWebEventConverter* + CreateConverterForModel(NPEventModel event_model); + + private: + DISALLOW_COPY_AND_ASSIGN(PluginWebEventConverterFactory); +}; + +#endif // WEBKIT_GLUE_PLUGIN_PLUGIN_WEB_EVENT_CONVERTER_MAC_H_ diff --git a/webkit/glue/plugins/plugin_web_event_converter_mac.mm b/webkit/glue/plugins/plugin_web_event_converter_mac.mm new file mode 100644 index 0000000..d8b5c05 --- /dev/null +++ b/webkit/glue/plugins/plugin_web_event_converter_mac.mm @@ -0,0 +1,396 @@ +// Copyright (c) 2010 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. + +#import <Cocoa/Cocoa.h> + +#include "base/logging.h" +#include "webkit/glue/plugins/plugin_web_event_converter_mac.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" + +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; + +namespace { + +// Returns true if the caps lock flag should be set for the given event. +// TODO: Ideally the event itself would know about the caps lock key; see +// <http://crbug.com/38226>. This function is only a temporary workaround that +// guesses based on live state. +bool CapsLockIsActive(const WebInputEvent& event) { + NSUInteger current_flags = [[NSApp currentEvent] modifierFlags]; + bool caps_lock_on = (current_flags & NSAlphaShiftKeyMask) ? true : false; + // If this a caps lock keypress, then the event stream state can be wrong. + // Luckily, the weird event stream for caps lock makes it easy to tell whether + // caps lock is being turned on or off. + if (event.type == WebInputEvent::KeyDown || + event.type == WebInputEvent::KeyUp) { + const WebKeyboardEvent* key_event = + static_cast<const WebKeyboardEvent*>(&event); + if (key_event->nativeKeyCode == 57) + caps_lock_on = (event.type == WebInputEvent::KeyDown); + } + return caps_lock_on; +} + +} // namespace + +#pragma mark - + +#ifndef NP_NO_CARBON + +// Converter implementation for the Carbon event model. +class CarbonPluginWebEventConverter : public PluginWebEventConverter { + public: + CarbonPluginWebEventConverter() {} + virtual ~CarbonPluginWebEventConverter() {} + + virtual bool InitWithEvent(const WebInputEvent& web_event); + + virtual void SetZoomLevel(float zooom); + + virtual void* plugin_event() { return &carbon_event_; } + + protected: + virtual bool ConvertKeyboardEvent(const WebKeyboardEvent& key_event); + virtual bool ConvertMouseEvent(const WebMouseEvent& mouse_event); + virtual bool ConvertMouseWheelEvent(const WebMouseWheelEvent& wheel_event); + + private: + // Returns the Carbon translation of web_event's modifiers. + static EventModifiers CarbonModifiers(const WebInputEvent& web_event); + + NPEvent carbon_event_; + + DISALLOW_COPY_AND_ASSIGN(CarbonPluginWebEventConverter); +}; + +bool CarbonPluginWebEventConverter::InitWithEvent( + const WebInputEvent& web_event) { + memset(&carbon_event_, 0, sizeof(carbon_event_)); + // Set the fields common to all event types. + carbon_event_.when = TickCount(); + carbon_event_.modifiers |= CarbonModifiers(web_event); + + return PluginWebEventConverter::InitWithEvent(web_event); +} + +void CarbonPluginWebEventConverter::SetZoomLevel(float zoom) { + // Nothing to do here; because the event is built using the WebMouseEvent's + // global coordinates, and the dummy window is in the right place, zoom has + // no effect. +} + +bool CarbonPluginWebEventConverter::ConvertKeyboardEvent( + const WebKeyboardEvent& key_event) { + // TODO: Figure out how to handle Unicode input to plugins, if that's + // even possible in the NPAPI Carbon event model. + carbon_event_.message = (key_event.nativeKeyCode << 8) & keyCodeMask; + carbon_event_.message |= key_event.text[0] & charCodeMask; + carbon_event_.modifiers |= btnState; + + switch (key_event.type) { + case WebInputEvent::KeyDown: + if (key_event.modifiers & WebInputEvent::IsAutoRepeat) + carbon_event_.what = autoKey; + else + carbon_event_.what = keyDown; + return true; + case WebInputEvent::KeyUp: + carbon_event_.what = keyUp; + return true; + case WebInputEvent::RawKeyDown: + case WebInputEvent::Char: + // May be used eventually for IME, but currently not needed. + return false; + default: + NOTREACHED(); + return false; + } +} + +bool CarbonPluginWebEventConverter::ConvertMouseEvent( + const WebMouseEvent& mouse_event) { + carbon_event_.where.h = mouse_event.globalX; + carbon_event_.where.v = mouse_event.globalY; + + // Default to "button up"; override this for mouse down events below. + carbon_event_.modifiers |= btnState; + + switch (mouse_event.button) { + case WebMouseEvent::ButtonLeft: + break; + case WebMouseEvent::ButtonMiddle: + carbon_event_.modifiers |= cmdKey; + break; + case WebMouseEvent::ButtonRight: + carbon_event_.modifiers |= controlKey; + break; + default: + NOTIMPLEMENTED(); + } + switch (mouse_event.type) { + case WebInputEvent::MouseMove: + carbon_event_.what = nullEvent; + return true; + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + carbon_event_.what = NPEventType_AdjustCursorEvent; + return true; + case WebInputEvent::MouseDown: + carbon_event_.modifiers &= ~btnState; + carbon_event_.what = mouseDown; + return true; + case WebInputEvent::MouseUp: + carbon_event_.what = mouseUp; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool CarbonPluginWebEventConverter::ConvertMouseWheelEvent( + const WebMouseWheelEvent& wheel_event) { + return false; // The Carbon NPAPI event model has no "mouse wheel" concept. +} + +EventModifiers CarbonPluginWebEventConverter::CarbonModifiers( + const WebInputEvent& web_event) { + NSInteger modifiers = 0; + if (web_event.modifiers & WebInputEvent::ControlKey) + modifiers |= controlKey; + if (web_event.modifiers & WebInputEvent::ShiftKey) + modifiers |= shiftKey; + if (web_event.modifiers & WebInputEvent::AltKey) + modifiers |= optionKey; + if (web_event.modifiers & WebInputEvent::MetaKey) + modifiers |= cmdKey; + if (CapsLockIsActive(web_event)) + modifiers |= alphaLock; + return modifiers; +} + +#endif // !NP_NO_CARBON + +#pragma mark - + +// Converter implementation for the Cocoa event model. +class CocoaPluginWebEventConverter : public PluginWebEventConverter { +public: + CocoaPluginWebEventConverter() {} + virtual ~CocoaPluginWebEventConverter() {} + + virtual bool InitWithEvent(const WebInputEvent& web_event); + + virtual void SetZoomLevel(float zoom); + + virtual void* plugin_event() { return &cocoa_event_; } + +protected: + virtual bool ConvertKeyboardEvent(const WebKeyboardEvent& key_event); + virtual bool ConvertMouseEvent(const WebMouseEvent& mouse_event); + virtual bool ConvertMouseWheelEvent(const WebMouseWheelEvent& wheel_event); + +private: + // Returns the Cocoa translation of web_event's modifiers. + static NSUInteger CocoaModifiers(const WebInputEvent& web_event); + + // Returns true if the given key is a modifier key. + static bool KeyIsModifier(int native_key_code); + + NPCocoaEvent cocoa_event_; + + DISALLOW_COPY_AND_ASSIGN(CocoaPluginWebEventConverter); +}; + +bool CocoaPluginWebEventConverter::InitWithEvent( + const WebInputEvent& web_event) { + memset(&cocoa_event_, 0, sizeof(cocoa_event_)); + return PluginWebEventConverter::InitWithEvent(web_event); +} + +void CocoaPluginWebEventConverter::SetZoomLevel(float zoom) { + // Make sure we are dealing with a mouse event. + if (!(cocoa_event_.type != NPCocoaEventMouseDown || + cocoa_event_.type != NPCocoaEventMouseUp || + cocoa_event_.type != NPCocoaEventMouseMoved || + cocoa_event_.type != NPCocoaEventMouseEntered || + cocoa_event_.type != NPCocoaEventMouseExited || + cocoa_event_.type != NPCocoaEventMouseDragged || + cocoa_event_.type != NPCocoaEventScrollWheel)) { + NOTREACHED(); + return; + } + cocoa_event_.data.mouse.pluginX = + round(cocoa_event_.data.mouse.pluginX * zoom); + cocoa_event_.data.mouse.pluginY = + round(cocoa_event_.data.mouse.pluginY * zoom); +} + +bool CocoaPluginWebEventConverter::ConvertKeyboardEvent( + const WebKeyboardEvent& key_event) { + cocoa_event_.data.key.keyCode = key_event.nativeKeyCode; + + cocoa_event_.data.key.modifierFlags |= CocoaModifiers(key_event); + + // Modifier keys have their own event type, and don't get character or + // repeat data. + if (KeyIsModifier(key_event.nativeKeyCode)) { + cocoa_event_.type = NPCocoaEventFlagsChanged; + return true; + } + + cocoa_event_.data.key.characters = reinterpret_cast<NPNSString*>( + [NSString stringWithFormat:@"%S", key_event.text]); + cocoa_event_.data.key.charactersIgnoringModifiers = + reinterpret_cast<NPNSString*>( + [NSString stringWithFormat:@"%S", key_event.unmodifiedText]); + + if (key_event.modifiers & WebInputEvent::IsAutoRepeat) + cocoa_event_.data.key.isARepeat = true; + + switch (key_event.type) { + case WebInputEvent::KeyDown: + cocoa_event_.type = NPCocoaEventKeyDown; + return true; + case WebInputEvent::KeyUp: + cocoa_event_.type = NPCocoaEventKeyUp; + return true; + case WebInputEvent::RawKeyDown: + case WebInputEvent::Char: + // May be used eventually for IME, but currently not needed. + return false; + default: + NOTREACHED(); + return false; + } +} + +bool CocoaPluginWebEventConverter::ConvertMouseEvent( + const WebMouseEvent& mouse_event) { + cocoa_event_.data.mouse.pluginX = mouse_event.x; + cocoa_event_.data.mouse.pluginY = mouse_event.y; + cocoa_event_.data.mouse.modifierFlags |= CocoaModifiers(mouse_event); + cocoa_event_.data.mouse.clickCount = mouse_event.clickCount; + switch (mouse_event.button) { + case WebMouseEvent::ButtonLeft: + cocoa_event_.data.mouse.buttonNumber = 0; + break; + case WebMouseEvent::ButtonMiddle: + cocoa_event_.data.mouse.buttonNumber = 2; + break; + case WebMouseEvent::ButtonRight: + cocoa_event_.data.mouse.buttonNumber = 1; + break; + default: + cocoa_event_.data.mouse.buttonNumber = mouse_event.button; + break; + } + switch (mouse_event.type) { + case WebInputEvent::MouseDown: + cocoa_event_.type = NPCocoaEventMouseDown; + return true; + case WebInputEvent::MouseUp: + cocoa_event_.type = NPCocoaEventMouseUp; + return true; + case WebInputEvent::MouseMove: { + bool mouse_is_down = + (mouse_event.modifiers & WebInputEvent::LeftButtonDown) || + (mouse_event.modifiers & WebInputEvent::RightButtonDown) || + (mouse_event.modifiers & WebInputEvent::MiddleButtonDown); + cocoa_event_.type = mouse_is_down ? NPCocoaEventMouseDragged + : NPCocoaEventMouseMoved; + return true; + } + case WebInputEvent::MouseEnter: + cocoa_event_.type = NPCocoaEventMouseEntered; + return true; + case WebInputEvent::MouseLeave: + cocoa_event_.type = NPCocoaEventMouseExited; + return true; + default: + NOTREACHED(); + return false; + } +} + +bool CocoaPluginWebEventConverter::ConvertMouseWheelEvent( + const WebMouseWheelEvent& wheel_event) { + cocoa_event_.type = NPCocoaEventScrollWheel; + cocoa_event_.data.mouse.pluginX = wheel_event.x; + cocoa_event_.data.mouse.pluginY = wheel_event.y; + cocoa_event_.data.mouse.modifierFlags |= CocoaModifiers(wheel_event); + cocoa_event_.data.mouse.deltaX = wheel_event.deltaX; + cocoa_event_.data.mouse.deltaY = wheel_event.deltaY; + return true; +} + +NSUInteger CocoaPluginWebEventConverter::CocoaModifiers( + const WebInputEvent& web_event) { + NSInteger modifiers = 0; + if (web_event.modifiers & WebInputEvent::ControlKey) + modifiers |= NSControlKeyMask; + if (web_event.modifiers & WebInputEvent::ShiftKey) + modifiers |= NSShiftKeyMask; + if (web_event.modifiers & WebInputEvent::AltKey) + modifiers |= NSAlternateKeyMask; + if (web_event.modifiers & WebInputEvent::MetaKey) + modifiers |= NSCommandKeyMask; + if (CapsLockIsActive(web_event)) + modifiers |= NSAlphaShiftKeyMask; + return modifiers; +} + +bool CocoaPluginWebEventConverter::KeyIsModifier(int native_key_code) { + switch (native_key_code) { + case 55: // Left command + case 54: // Right command + case 58: // Left option + case 61: // Right option + case 59: // Left control + case 62: // Right control + case 56: // Left shift + case 60: // Right shift + case 57: // Caps lock + return true; + default: + return false; + } +} + +#pragma mark - + +bool PluginWebEventConverter::InitWithEvent(const WebInputEvent& web_event) { + if (web_event.type == WebInputEvent::MouseWheel) { + return ConvertMouseWheelEvent( + *static_cast<const WebMouseWheelEvent*>(&web_event)); + } else if (WebInputEvent::isMouseEventType(web_event.type)) { + return ConvertMouseEvent(*static_cast<const WebMouseEvent*>(&web_event)); + } else if (WebInputEvent::isKeyboardEventType(web_event.type)) { + return ConvertKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&web_event)); + } + DLOG(WARNING) << "Unknown event type " << web_event.type; + return false; +} + +#pragma mark - + +PluginWebEventConverter* + PluginWebEventConverterFactory::CreateConverterForModel( + NPEventModel event_model) { + switch (event_model) { + case NPEventModelCocoa: + return new CocoaPluginWebEventConverter(); +#ifndef NP_NO_CARBON + case NPEventModelCarbon: + return new CarbonPluginWebEventConverter(); +#endif + default: + NOTIMPLEMENTED(); + return NULL; + } +} diff --git a/webkit/glue/plugins/ppb_private.h b/webkit/glue/plugins/ppb_private.h new file mode 100644 index 0000000..a0956f0 --- /dev/null +++ b/webkit/glue/plugins/ppb_private.h @@ -0,0 +1,21 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_PPB_PRIVATE_H_ +#define WEBKIT_GLUE_PLUGINS_PPB_PRIVATE_H_ + +#include "third_party/ppapi/c/pp_var.h" + +#define PPB_PRIVATE_INTERFACE "PPB_Private;1" + +typedef enum _pp_ResourceString { + PP_RESOURCESTRING_PDFGETPASSWORD = 0, +} PP_ResourceString; + +typedef struct _ppb_Private { + // Returns a localized string. + PP_Var (*GetLocalizedString)(PP_ResourceString string_id); +} PPB_Private; + +#endif // WEBKIT_GLUE_PLUGINS_PPB_PRIVATE_H_ diff --git a/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc b/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc new file mode 100644 index 0000000..424cc1e --- /dev/null +++ b/webkit/glue/plugins/quickdraw_drawing_manager_mac.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2010 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 NP_NO_QUICKDRAW + +#include "webkit/glue/plugins/quickdraw_drawing_manager_mac.h" + +#include "webkit/glue/plugins/coregraphics_private_symbols_mac.h" + +// Turn off GCC warnings about deprecated functions (since QuickDraw is a +// deprecated API). According to the GCC documentation, this can only be done +// per file, not pushed and popped like some options can be. +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +QuickDrawDrawingManager::QuickDrawDrawingManager() + : plugin_window_(NULL), target_context_(NULL), fast_path_enabled_(false), + current_port_(NULL), target_world_(NULL), plugin_world_(NULL) {} + +QuickDrawDrawingManager::~QuickDrawDrawingManager() { + DestroyGWorlds(); +} + +void QuickDrawDrawingManager::SetFastPathEnabled(bool enabled) { + if (fast_path_enabled_ == enabled) + return; + + fast_path_enabled_ = enabled; + if (enabled) { + if (!target_world_) + UpdateGWorlds(); + // Copy our last window snapshot into our new source, since the plugin + // may not repaint everything. + CopyGWorldBits(target_world_, plugin_world_, plugin_size_); + current_port_ = plugin_world_; + } else { + current_port_ = GetWindowPort(plugin_window_); + } +} + +void QuickDrawDrawingManager::SetTargetContext(CGContextRef context, + const gfx::Size& plugin_size) { + target_context_ = context; + if (plugin_size != plugin_size_) { + plugin_size_ = plugin_size; + // Pitch the old GWorlds, since they are the wrong size now. + DestroyGWorlds(); + if (fast_path_enabled_) + UpdateGWorlds(); + } +} + +void QuickDrawDrawingManager::SetPluginWindow(WindowRef window) { + plugin_window_ = window; + if (!fast_path_enabled_) + current_port_ = GetWindowPort(window); +} + +void QuickDrawDrawingManager::UpdateContext() { + if (fast_path_enabled_) + CopyGWorldBits(plugin_world_, target_world_, plugin_size_); + else + ScrapeWindow(plugin_window_, target_context_, plugin_size_); +} + +bool QuickDrawDrawingManager::IsFastPathEnabled() { + return fast_path_enabled_; +} + +void QuickDrawDrawingManager::MakePortCurrent() { + if (fast_path_enabled_) + SetGWorld(current_port_, NULL); + else + SetPort(current_port_); +} + +void QuickDrawDrawingManager::DestroyGWorlds() { + if (plugin_world_) { + DisposeGWorld(plugin_world_); + plugin_world_ = NULL; + } + if (target_world_) { + DisposeGWorld(target_world_); + target_world_ = NULL; + } +} + +void QuickDrawDrawingManager::UpdateGWorlds() { + DestroyGWorlds(); + if (!target_context_) + return; + + Rect window_bounds = { + 0, 0, plugin_size_.height(), plugin_size_.width() + }; + // Create a GWorld pointing at the same bits as our target context. + if (target_context_) { + NewGWorldFromPtr( + &target_world_, k32BGRAPixelFormat, &window_bounds, NULL, NULL, 0, + static_cast<Ptr>(CGBitmapContextGetData(target_context_)), + static_cast<SInt32>(CGBitmapContextGetBytesPerRow(target_context_))); + } + // Create a GWorld for the plugin to paint into whenever it wants; since + // QuickDraw plugins don't draw at known times, they can't be allowed to draw + // directly into the shared memory. + NewGWorld(&plugin_world_, k32ARGBPixelFormat, &window_bounds, + NULL, NULL, kNativeEndianPixMap); + if (fast_path_enabled_) + current_port_ = plugin_world_; +} + +void QuickDrawDrawingManager::ScrapeWindow(WindowRef window, + CGContextRef target_context, + const gfx::Size& plugin_size) { + if (!target_context) + return; + + CGRect window_bounds = CGRectMake(0, 0, + plugin_size.width(), + plugin_size.height()); + CGWindowID window_id = HIWindowGetCGWindowID(window); + CGContextSaveGState(target_context); + CGContextTranslateCTM(target_context, 0, plugin_size.height()); + CGContextScaleCTM(target_context, 1.0, -1.0); + CGContextCopyWindowCaptureContentsToRect(target_context, window_bounds, + _CGSDefaultConnection(), + window_id, 0); + CGContextRestoreGState(target_context); +} + +void QuickDrawDrawingManager::CopyGWorldBits(GWorldPtr source, GWorldPtr dest, + const gfx::Size& plugin_size) { + if (!(source && dest)) + return; + + Rect window_bounds = { 0, 0, plugin_size.height(), plugin_size.width() }; + PixMapHandle source_pixmap = GetGWorldPixMap(source); + if (LockPixels(source_pixmap)) { + PixMapHandle dest_pixmap = GetGWorldPixMap(dest); + if (LockPixels(dest_pixmap)) { + SetGWorld(dest, NULL); + // Set foreground and background colors to avoid "colorizing" the image. + ForeColor(blackColor); + BackColor(whiteColor); + CopyBits(reinterpret_cast<BitMap*>(*source_pixmap), + reinterpret_cast<BitMap*>(*dest_pixmap), + &window_bounds, &window_bounds, srcCopy, NULL); + UnlockPixels(dest_pixmap); + } + UnlockPixels(source_pixmap); + } +} + +#endif // !NP_NO_QUICKDRAW diff --git a/webkit/glue/plugins/quickdraw_drawing_manager_mac.h b/webkit/glue/plugins/quickdraw_drawing_manager_mac.h new file mode 100644 index 0000000..8163f92 --- /dev/null +++ b/webkit/glue/plugins/quickdraw_drawing_manager_mac.h @@ -0,0 +1,83 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_QUICKDRAW_DRAWING_MANAGER_MAC_H_ +#define WEBKIT_GLUE_QUICKDRAW_DRAWING_MANAGER_MAC_H_ + +#ifndef NP_NO_QUICKDRAW + +#import <Carbon/Carbon.h> + +#include "gfx/rect.h" + +// Plugin helper class encapsulating the details of capturing what a QuickDraw +// drawing model plugin draws, then drawing it into a CGContext. +class QuickDrawDrawingManager { + public: + QuickDrawDrawingManager(); + ~QuickDrawDrawingManager(); + + // Sets the mode used for plugin drawing. If enabled is true the plugin draws + // into a GWorld that's not connected to a window, otherwise the plugin draws + // into our the plugin's dummy window (which is slower, since the call we use + // to scrape the window contents is much more expensive than copying between + // GWorlds). + void SetFastPathEnabled(bool enabled); + + // Returns true if the fast path is currently enabled. + bool IsFastPathEnabled(); + + // Sets the context that the plugin bits should be copied into when + // UpdateContext is called. This object does not retain |context|, so the + // caller must call SetTargetContext again if the context changes. + // If the fast path is currently enabled, this call will cause the port to + // change. + void SetTargetContext(CGContextRef context, const gfx::Size& plugin_size); + + // Sets the window that is used by the plugin. This object does not own the + // window, so the caler must call SetPluginWindow again if the window changes. + void SetPluginWindow(WindowRef window); + + // Updates the target context with the current plugin bits. + void UpdateContext(); + + // Returns the port that the plugin should draw into. This returned port is + // only valid until the next call to SetFastPathEnabled (or SetTargetContext + // while the fast path is enabled). + CGrafPtr port() { return current_port_; } + + // Makes the QuickDraw port current; should be called before calls where the + // plugin might draw. + void MakePortCurrent(); + + private: + // Updates the GWorlds used by the faster path. + void UpdateGWorlds(); + + // Deletes the GWorlds used by the faster path. + void DestroyGWorlds(); + + // Scrapes the contents of the window into the given context. + // Used for the slower path. + static void ScrapeWindow(WindowRef window, CGContextRef target_context, + const gfx::Size& plugin_size); + + // Copies the source GWorld's bits into the target GWorld. + // Used for the faster path. + static void CopyGWorldBits(GWorldPtr source, GWorldPtr dest, + const gfx::Size& plugin_size); + + WindowRef plugin_window_; // Weak reference. + CGContextRef target_context_; // Weak reference. + gfx::Size plugin_size_; + bool fast_path_enabled_; + CGrafPtr current_port_; + // Variables used for the faster path: + GWorldPtr target_world_; // Created lazily; may be NULL. + GWorldPtr plugin_world_; // Created lazily; may be NULL. +}; + +#endif // !NP_NO_QUICKDRAW + +#endif // QUICKDRAW_DRAWING_MANAGER_MAC diff --git a/webkit/glue/plugins/test/Info.plist b/webkit/glue/plugins/test/Info.plist new file mode 100644 index 0000000..37145fd --- /dev/null +++ b/webkit/glue/plugins/test/Info.plist @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>NPAPITestPlugIn</string> + <key>CFBundleIdentifier</key> + <string>org.chromium.npapi_test_plugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>CFPlugInDynamicRegisterFunction</key> + <string></string> + <key>CFPlugInDynamicRegistration</key> + <string>NO</string> + <key>WebPluginDescription</key> + <string>Simple NPAPI plug-in for Chromium unit tests</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/vnd.npapi-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>npapitest</string> + </array> + <key>WebPluginTypeDescription</key> + <string>test npapi</string> + </dict> + </dict> + <key>WebPluginName</key> + <string>Chromium NPAPI Test Plugin</string> +</dict> +</plist> diff --git a/webkit/glue/plugins/test/npapi_constants.cc b/webkit/glue/plugins/test/npapi_constants.cc new file mode 100644 index 0000000..75cc68f --- /dev/null +++ b/webkit/glue/plugins/test/npapi_constants.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/npapi_constants.h" + +namespace NPAPIClient { +const char kTestCompleteCookie[] = "status"; +const char kTestCompleteSuccess[] = "OK"; +} diff --git a/webkit/glue/plugins/test/npapi_constants.h b/webkit/glue/plugins/test/npapi_constants.h new file mode 100644 index 0000000..6570c35 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_constants.h @@ -0,0 +1,19 @@ +// Copyright (c) 2006-2008 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 for the NPAPI test + +#ifndef WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ +#define WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ + +namespace NPAPIClient { +// The name of the cookie which will be used to communicate between +// the plugin and the test harness. +extern const char kTestCompleteCookie[]; + +// The cookie value which will be sent to the client upon successful +// test. +extern const char kTestCompleteSuccess[]; +} +#endif // WEBKIT_PORT_PLUGINS_TEST_NPAPI_CONSTANTS_H__ diff --git a/webkit/glue/plugins/test/npapi_test.cc b/webkit/glue/plugins/test/npapi_test.cc new file mode 100644 index 0000000..d7c4fa7 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2006-2008 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. + +// +// npapitest +// +// This is a NPAPI Plugin Program which is used to test the Browser's NPAPI +// host implementation. It is used in conjunction with the npapi_unittest. +// +// As a NPAPI Plugin, you can invoke it by creating a web page of the following +// type: +// +// <embed src="content-to-load" type="application/vnd.npapi-test" +// name="test-name"> +// +// arguments: +// src: This is the initial content which will be sent to the plugin. +// type: Must be "application/vnd.npapi-test" +// name: The testcase to run when invoked +// id: The id of the test being run (for testing concurrent plugins) +// +// The Plugin drives the actual test, calling host functions and validating the +// Host callbacks which it receives. It is the duty of the plugin to record +// all errors. +// +// To indicate test completion, the plugin expects the containing HTML page to +// implement two javascript functions: +// onSuccess(string testname); +// onFailure(string testname, string results); +// The HTML host pages used in this test will then set a document cookie +// which the automated test framework can poll for and discover that the +// test has completed. +// +// +// TESTS +// When the PluginClient receives a NPP_New callback from the browser, +// it looks at the "name" argument which is passed in. It verifies that +// the name matches a known test, and instantiates that test. The test is +// a subclass of PluginTest. +// +// + +#include "base/basictypes.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define EXPORT __attribute__((visibility ("default"))) +#else +#define EXPORT +#endif + +#include "webkit/glue/plugins/test/plugin_client.h" + +#if defined(OS_WIN) +BOOL API_CALL DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) { + return TRUE; +} +#endif + +extern "C" { +EXPORT NPError API_CALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) { + return NPAPIClient::PluginClient::GetEntryPoints(pFuncs); +} + +EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* pFuncs) { + return NPAPIClient::PluginClient::Initialize(pFuncs); +} + +EXPORT NPError API_CALL NP_Shutdown() { + return NPAPIClient::PluginClient::Shutdown(); +} +} // extern "C" + +namespace WebCore { + const char* currentTextBreakLocaleID() { return "en_us"; } +} diff --git a/webkit/glue/plugins/test/npapi_test.def b/webkit/glue/plugins/test/npapi_test.def new file mode 100644 index 0000000..4481c16 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.def @@ -0,0 +1,6 @@ +LIBRARY npapi_test_plugin + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 diff --git a/webkit/glue/plugins/test/npapi_test.rc b/webkit/glue/plugins/test/npapi_test.rc new file mode 100644 index 0000000..524dda4 --- /dev/null +++ b/webkit/glue/plugins/test/npapi_test.rc @@ -0,0 +1,102 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "FileDescription", "NPAPI Test Plugin" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "npapi_test_plugin" + VALUE "LegalCopyright", "Copyright (C) 2007" + VALUE "MIMEType", "application/vnd.npapi-test" + VALUE "OriginalFilename", "npapi_test_plugin.dll" + VALUE "ProductName", "NPAPI Test Plugin" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/webkit/glue/plugins/test/plugin_arguments_test.cc b/webkit/glue/plugins/test/plugin_arguments_test.cc new file mode 100644 index 0000000..ee6d2c0 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_arguments_test.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2006-2008 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/basictypes.h" +#include "base/string_util.h" + +#include "webkit/glue/plugins/test/plugin_arguments_test.h" + +namespace NPAPIClient { + +PluginArgumentsTest::PluginArgumentsTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginArgumentsTest::New(uint16 mode, int16 argc, + const char* argn[], const char* argv[], + NPSavedData* saved) { + // mode: should be the string either "NP_EMBED" or "NP_FULL", + // depending on the mode passed in. + // count: the count of "val" arguments. If the value is + // 2, then we'll find arguments "val1" and "val2". If + // the value is 0, then there will be no "val" arguments. + // size: each val string will be this size * the value's + // index. E.g if size is "10", val1 will be 10bytes, + // and val2 will be 20bytes. + const char *mode_string = GetArgValue("mode", argc, argn, argv); + ExpectAsciiStringNotEqual(mode_string, (const char *)NULL); + if (mode_string != NULL) { + std::string mode_dep_string = mode_string; + if (mode == NP_EMBED) + ExpectStringLowerCaseEqual(mode_dep_string, "np_embed"); + else if (mode == NP_FULL) + ExpectStringLowerCaseEqual(mode_dep_string, "np_full"); + } + + const char *count_string = GetArgValue("count", argc, argn, argv); + if (count_string != NULL) { + int max_args = atoi(count_string); + + const char *size_string = GetArgValue("size", argc, argn, argv); + ExpectAsciiStringNotEqual(size_string, (const char *)NULL); + if (size_string != NULL) { + int size = atoi(size_string); + + for (int index = 1; index <= max_args; index++) { + std::string arg_name = StringPrintf("%s%d", "val", index); + const char *val_string = GetArgValue(arg_name.c_str(), argc, argn, + argv); + ExpectAsciiStringNotEqual(val_string, (const char*)NULL); + if (val_string != NULL) + ExpectIntegerEqual((int)strlen(val_string), (index*size)); + } + } + } + + return PluginTest::New(mode, argc, argn, argv, saved); +} + +NPError PluginArgumentsTest::SetWindow(NPWindow* pNPWindow) { + // This test just tests the arguments. We're done now. + this->SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_arguments_test.h b/webkit/glue/plugins/test/plugin_arguments_test.h new file mode 100644 index 0000000..aa05f19 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_arguments_test.h @@ -0,0 +1,43 @@ +// Copyright (c) 2006-2008 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 WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginArgumentsTest test that we properly receive arguments +// intended for the plugin. +// +// This is basically overkill for testing that the arguments passed +// to the plugin match what we expect. +// +// We expect to find the following arguments: +// mode: should be the string either "NP_EMBED" or "NP_FULL", +// depending on the mode passed in. +// count: the count of "val" arguments. If the value is +// 2, then we'll find arguments "val1" and "val2". If +// the value is 0, then there will be no "val" arguments. +// size: each val string will be this size * the value's +// index. E.g if size is "10", val1 will be 10bytes, +// and val2 will be 20bytes. +// +class PluginArgumentsTest : public PluginTest { + public: + // Constructor. + PluginArgumentsTest(NPP id, NPNetscapeFuncs *host_functions); + + // Initialize this PluginTest based on the arguments from NPP_New. + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_ARGUMENTS_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_client.cc b/webkit/glue/plugins/test/plugin_client.cc new file mode 100644 index 0000000..d617e16 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_client.cc @@ -0,0 +1,230 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/test/plugin_client.h" + +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_test.h" +#include "webkit/glue/plugins/test/plugin_test_factory.h" + +namespace NPAPIClient { + +NPNetscapeFuncs* PluginClient::host_functions_; + +NPError PluginClient::GetEntryPoints(NPPluginFuncs* pFuncs) { + if (pFuncs == NULL) + return NPERR_INVALID_FUNCTABLE_ERROR; + + if (pFuncs->size < sizeof(NPPluginFuncs)) + return NPERR_INVALID_FUNCTABLE_ERROR; + + pFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->asfile = NPP_StreamAsFile; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->javaClass = NULL; + + return NPERR_NO_ERROR; +} + +NPError PluginClient::Initialize(NPNetscapeFuncs* pFuncs) { + if (pFuncs == NULL) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + if (static_cast<unsigned char>((pFuncs->version >> 8) & 0xff) > + NP_VERSION_MAJOR) { + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + +#if defined(OS_WIN) + // Check if we should crash. + HANDLE crash_event = CreateEvent(NULL, TRUE, FALSE, L"TestPluginCrashOnInit"); + if (WaitForSingleObject(crash_event, 0) == WAIT_OBJECT_0) { + int *zero = NULL; + *zero = 0; + } + CloseHandle(crash_event); +#endif + + host_functions_ = pFuncs; + + return NPERR_NO_ERROR; +} + +NPError PluginClient::Shutdown() { + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient + +extern "C" { +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, + int16 argc, char* argn[], char* argv[], NPSavedData* saved) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // We look at the test name requested via the plugin arguments. We match + // that against a given test and try to instantiate it. + + // lookup the name parameter + std::string test_name; + for (int name_index = 0; name_index < argc; name_index++) { + if (base::strcasecmp(argn[name_index], "name") == 0) { + test_name = argv[name_index]; + break; + } + } + if (test_name.empty()) + return NPERR_GENERIC_ERROR; // no name found + + NPAPIClient::PluginTest* new_test = NPAPIClient::CreatePluginTest(test_name, + instance, NPAPIClient::PluginClient::HostFunctions()); + if (new_test == NULL) { + // If we don't have a test case for this, create a + // generic one which basically never fails. + LOG(WARNING) << "Unknown test name '" << test_name + << "'; using default test."; + new_test = new NPAPIClient::PluginTest(instance, + NPAPIClient::PluginClient::HostFunctions()); + } + + NPError ret = new_test->New(mode, argc, (const char**)argn, + (const char**)argv, saved); + if ((ret == NPERR_NO_ERROR) && new_test->IsWindowless()) { + NPAPIClient::PluginClient::HostFunctions()->setvalue( + instance, NPPVpluginWindowBool, NULL); + } + + return ret; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + NPError rv = plugin->Destroy(); + delete plugin; + return rv; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* pNPWindow) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->SetWindow(pNPWindow); +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, + NPStream* stream, NPBool seekable, uint16* stype) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->NewStream(type, stream, seekable, stype); +} + +int32 NPP_WriteReady(NPP instance, NPStream *stream) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->WriteReady(stream); +} + +int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, + int32 len, void *buffer) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->Write(stream, offset, len, buffer); +} + +NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->DestroyStream(stream, reason); +} + +void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname) { + if (instance == NULL) + return; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->StreamAsFile(stream, fname); +} + +void NPP_Print(NPP instance, NPPrint* printInfo) { + if (instance == NULL) + return; + + // XXXMB - do work here. +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + if (instance == NULL) + return; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->URLNotify(url, reason, notifyData); +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void *value) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // XXXMB - do work here. + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void *value) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + // XXXMB - do work here. + return NPERR_GENERIC_ERROR; +} + +int16 NPP_HandleEvent(NPP instance, void* event) { + if (instance == NULL) + return 0; + + NPAPIClient::PluginTest *plugin = + (NPAPIClient::PluginTest*)instance->pdata; + + return plugin->HandleEvent(event); +} +} // extern "C" diff --git a/webkit/glue/plugins/test/plugin_client.h b/webkit/glue/plugins/test/plugin_client.h new file mode 100644 index 0000000..a6291b0 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_client.h @@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 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 WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ + +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +// A PluginClient is a NPAPI Plugin. This class contains +// the bootstrapping functions used by the browser to load +// the plugin. +class PluginClient { + public: + // Although not documented in the NPAPI specification, this function + // gets the list of entry points in the NPAPI Plugin (client) for the + // NPAPI Host to call. + static NPError GetEntryPoints(NPPluginFuncs* pFuncs); + + // The browser calls this function only once: when a plug-in is loaded, + // before the first instance is created. This is the first function that + // the browser calls. NP_Initialize tells the plug-in that the browser has + // loaded it and provides global initialization. Allocate any memory or + // resources shared by all instances of your plug-in at this time. + static NPError Initialize(NPNetscapeFuncs* pFuncs); + + // The browser calls this function once after the last instance of your + // plug-in is destroyed, before unloading the plug-in library itself. Use + // NP_Shutdown to delete any data allocated in NP_Initialize to be shared + // by all instances of a plug-in. + static NPError Shutdown(); + + // The table of functions provided by the host. + static NPNetscapeFuncs *HostFunctions() { return host_functions_; } + + private: + static NPNetscapeFuncs* host_functions_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_CLIENT_H__ diff --git a/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc b/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc new file mode 100644 index 0000000..0bea703 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_create_instance_in_paint.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/test/plugin_create_instance_in_paint.h" + +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +CreateInstanceInPaintTest::CreateInstanceInPaintTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + window_(NULL), created_(false) { +} + +NPError CreateInstanceInPaintTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (test_id() == "1") { + if (!window_) { + static ATOM window_class = 0; + if (!window_class) { + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = + &NPAPIClient::CreateInstanceInPaintTest::WindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = L"CreateInstanceInPaintTestWindowClass"; + wcex.hIconSm = 0; + window_class = RegisterClassEx(&wcex); + } + + HWND parent = reinterpret_cast<HWND>(pNPWindow->window); + window_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + MAKEINTATOM(window_class), 0, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE , + 0, 0, 100, 100, parent, 0, GetModuleHandle(NULL), 0); + DCHECK(window_); + ::SetProp(window_, L"Plugin_Instance", this); + } + } else if (test_id() == "2") { + SignalTestCompleted(); + } else { + NOTREACHED(); + } + return NPERR_NO_ERROR; +} + +LRESULT CALLBACK CreateInstanceInPaintTest::WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam) { + if (message == WM_PAINT) { + CreateInstanceInPaintTest* this_instance = + reinterpret_cast<CreateInstanceInPaintTest*> + (::GetProp(window, L"Plugin_Instance")); + if (this_instance->test_id() == "1" && !this_instance->created_) { + this_instance->created_ = true; + this_instance->HostFunctions()->geturlnotify( + this_instance->id(), "javascript:CreateNewInstance()", NULL, + reinterpret_cast<void*>(1)); + } + } + + return DefWindowProc(window, message, wparam, lparam); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_create_instance_in_paint.h b/webkit/glue/plugins/test/plugin_create_instance_in_paint.h new file mode 100644 index 0000000..84d7a94 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_create_instance_in_paint.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests that creating a new plugin via script while handling a +// Windows message doesn't cause a deadlock. +class CreateInstanceInPaintTest : public PluginTest { + public: + // Constructor. + CreateInstanceInPaintTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + + private: + static LRESULT CALLBACK WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam); + + HWND window_; + bool created_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_CREATE_INSTANCE_IN_PAINT_H diff --git a/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc new file mode 100644 index 0000000..15318b4 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h" + +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +#define kUrl "javascript:window.location+\"\"" +#define kUrlStreamId 1 + +DeletePluginInStreamTest::DeletePluginInStreamTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false) { +} + +NPError DeletePluginInStreamTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = "self_delete_plugin_stream.html"; + HostFunctions()->geturlnotify(id(), url.c_str(), NULL, + reinterpret_cast<void*>(kUrlStreamId)); + test_started_ = true; + } + return NPERR_NO_ERROR; +} + +NPError DeletePluginInStreamTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + NPIdentifier delete_id = HostFunctions()->getstringidentifier("DeletePluginWithinScript"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant rv; + HostFunctions()->invoke(id(), window_obj, delete_id, NULL, 0, &rv); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h new file mode 100644 index 0000000..418e976 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests +class DeletePluginInStreamTest : public PluginTest { + public: + // Constructor. + DeletePluginInStreamTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + private: + bool test_started_; + std::string self_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_DELETE_PLUGIN_IN_STREAM_TEST_H diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc new file mode 100644 index 0000000..d17dced --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/test/plugin_get_javascript_url2_test.h" + +#include "base/basictypes.h" + +// url for "self". +#define SELF_URL "javascript:window.location+\"\"" +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +const int kNPNEvaluateTimerID = 100; +const int kNPNEvaluateTimerElapse = 50; + +namespace NPAPIClient { + +ExecuteGetJavascriptUrl2Test::ExecuteGetJavascriptUrl2Test( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false) { +} + +NPError ExecuteGetJavascriptUrl2Test::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), "_self", + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + test_started_ = true; + } + return NPERR_NO_ERROR; +} + +NPError ExecuteGetJavascriptUrl2Test::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 ExecuteGetJavascriptUrl2Test::WriteReady(NPStream *stream) { + return STREAM_CHUNK; +} + +int32 ExecuteGetJavascriptUrl2Test::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError ExecuteGetJavascriptUrl2Test::DestroyStream(NPStream *stream, NPError reason) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void ExecuteGetJavascriptUrl2Test::URLNotify(const char* url, NPReason reason, void* data) { + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), + cast_validity_check); + + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + if (self_url_.empty()) + SetError("Failed to obtain window location."); + SignalTestCompleted(); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h new file mode 100644 index 0000000..557da76 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url2_test.h @@ -0,0 +1,38 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests NPP_GetURLNotify for a javascript URL with _top +// as the target frame. +class ExecuteGetJavascriptUrl2Test : public PluginTest { + public: + // Constructor. + ExecuteGetJavascriptUrl2Test(NPP id, NPNetscapeFuncs *host_functions); + + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void URLNotify(const char* url, NPReason reason, void* data); + + private: + bool test_started_; + std::string self_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL2_H diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc b/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc new file mode 100644 index 0000000..cc6bc19 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url_test.cc @@ -0,0 +1,210 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_get_javascript_url_test.h" + +#include "base/basictypes.h" + +// url for "self". +#define SELF_URL "javascript:window.location+\"\"" +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +const int kNPNEvaluateTimerID = 100; +const int kNPNEvaluateTimerElapse = 50; + + +namespace NPAPIClient { + +ExecuteGetJavascriptUrlTest::ExecuteGetJavascriptUrlTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + test_started_(false), +#ifdef OS_WIN + window_(NULL), +#endif + npn_evaluate_context_(false) { +} + +NPError ExecuteGetJavascriptUrlTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!test_started_) { + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), "_top", + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + test_started_ = true; + +#ifdef OS_WIN + HWND window_handle = reinterpret_cast<HWND>(pNPWindow->window); + if (!::GetProp(window_handle, L"Plugin_Instance")) { + ::SetProp(window_handle, L"Plugin_Instance", this); + // We attempt to retreive the NPObject for the plugin instance identified + // by the NPObjectLifetimeTestInstance2 class as it may not have been + // instantiated yet. + SetTimer(window_handle, kNPNEvaluateTimerID, kNPNEvaluateTimerElapse, + TimerProc); + } + window_ = window_handle; +#endif + } + + return NPERR_NO_ERROR; +} + +#ifdef OS_WIN +void CALLBACK ExecuteGetJavascriptUrlTest::TimerProc( + HWND window, UINT message, UINT timer_id, unsigned long elapsed_time) { + ExecuteGetJavascriptUrlTest* this_instance = + reinterpret_cast<ExecuteGetJavascriptUrlTest*> + (::GetProp(window, L"Plugin_Instance")); + + NPObject *window_obj = NULL; + this_instance->HostFunctions()->getvalue(this_instance->id(), + NPNVWindowNPObject, + &window_obj); + if (!window_obj) { + this_instance->SetError("Failed to get NPObject for plugin instance2"); + this_instance->SignalTestCompleted(); + return; + } + + std::string script = "javascript:window.location"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + NPVariant result_var; + + this_instance->npn_evaluate_context_ = true; + NPError result = this_instance->HostFunctions()->evaluate( + this_instance->id(), window_obj, &script_string, &result_var); + this_instance->npn_evaluate_context_ = false; +} +#endif + +NPError ExecuteGetJavascriptUrlTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + if (npn_evaluate_context_) { + SetError("NewStream received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 ExecuteGetJavascriptUrlTest::WriteReady(NPStream *stream) { + if (npn_evaluate_context_) { + SetError("WriteReady received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + return STREAM_CHUNK; +} + +int32 ExecuteGetJavascriptUrlTest::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + if (npn_evaluate_context_) { + SetError("Write received in context of NPN_Evaluate"); + return len; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError ExecuteGetJavascriptUrlTest::DestroyStream(NPStream *stream, NPError reason) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + +#ifdef OS_WIN + KillTimer(window_, kNPNEvaluateTimerID); +#endif + + if (npn_evaluate_context_) { + SetError("DestroyStream received in context of NPN_Evaluate"); + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void ExecuteGetJavascriptUrlTest::URLNotify(const char* url, NPReason reason, void* data) { + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), + cast_validity_check); + + if (npn_evaluate_context_) { + SetError("URLNotify received in context of NPN_Evaluate"); + return; + } + + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + if (self_url_.empty()) + SetError("Failed to obtain window location."); + SignalTestCompleted(); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_get_javascript_url_test.h b/webkit/glue/plugins/test/plugin_get_javascript_url_test.h new file mode 100644 index 0000000..5c2540d --- /dev/null +++ b/webkit/glue/plugins/test/plugin_get_javascript_url_test.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests NPP_GetURLNotify for a javascript URL with _top +// as the target frame. +class ExecuteGetJavascriptUrlTest : public PluginTest { + public: + // Constructor. + ExecuteGetJavascriptUrlTest(NPP id, NPNetscapeFuncs *host_functions); + // + // NPAPI functions + // + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void URLNotify(const char* url, NPReason reason, void* data); + + private: +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_time); +#endif + bool test_started_; + // This flag is set to true in the context of the NPN_Evaluate call. + bool npn_evaluate_context_; + std::string self_url_; + +#if defined(OS_WIN) + HWND window_; +#endif +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_GET_JAVASCRIPT_URL_H diff --git a/webkit/glue/plugins/test/plugin_geturl_test.cc b/webkit/glue/plugins/test/plugin_geturl_test.cc new file mode 100644 index 0000000..f321b4b --- /dev/null +++ b/webkit/glue/plugins/test/plugin_geturl_test.cc @@ -0,0 +1,374 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_geturl_test.h" + +#include <stdio.h> + +#include "base/basictypes.h" +#include "base/file_util.h" + +// url for "self". The %22%22 is to make a statement for javascript to +// evaluate and return. +#define SELF_URL "javascript:window.location+\"\"" + +// The identifier for the self url stream. +#define SELF_URL_STREAM_ID 1 + +// The identifier for the fetched url stream. +#define FETCHED_URL_STREAM_ID 2 + +// url for testing GetURL with a bogus URL. +#define BOGUS_URL "bogoproto:///x:/asdf.xysdhffieasdf.asdhj/" + +// The identifier for the bogus url stream. +#define BOGUS_URL_STREAM_ID 3 + +// The maximum chunk size of stream data. +#define STREAM_CHUNK 197 + +namespace NPAPIClient { + +PluginGetURLTest::PluginGetURLTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + tests_started_(false), + tests_in_progress_(0), + test_file_(NULL), + expect_404_response_(false), + npn_evaluate_context_(false) { +} + +NPError PluginGetURLTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + const char* page_not_found_url = GetArgValue("page_not_found_url", argc, + argn, argv); + if (page_not_found_url) { + page_not_found_url_ = page_not_found_url; + expect_404_response_ = true; + } + + const char* fail_write_url = GetArgValue("fail_write_url", argc, + argn, argv); + if (fail_write_url) { + fail_write_url_ = fail_write_url; + } + + const char* referrer_target_url = GetArgValue("ref_target", argc, + argn, argv); + if (referrer_target_url) { + referrer_target_url_ = referrer_target_url; + } + + return PluginTest::New(mode, argc, argn, argv, saved); +} + +NPError PluginGetURLTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!tests_started_) { + tests_started_ = true; + + tests_in_progress_++; + + if (expect_404_response_) { + HostFunctions()->geturl(id(), page_not_found_url_.c_str(), NULL); + return NPERR_NO_ERROR; + } else if (!fail_write_url_.empty()) { + HostFunctions()->geturl(id(), fail_write_url_.c_str(), NULL); + return NPERR_NO_ERROR; + } else if (!referrer_target_url_.empty()) { + HostFunctions()->pushpopupsenabledstate(id(), true); + HostFunctions()->geturl(id(), referrer_target_url_.c_str(), "_blank"); + HostFunctions()->poppopupsenabledstate(id()); + return NPERR_NO_ERROR; + } + + std::string url = SELF_URL; + HostFunctions()->geturlnotify(id(), url.c_str(), NULL, + reinterpret_cast<void*>(SELF_URL_STREAM_ID)); + + tests_in_progress_++; + std::string bogus_url = BOGUS_URL; + HostFunctions()->geturlnotify(id(), bogus_url.c_str(), NULL, + reinterpret_cast<void*>(BOGUS_URL_STREAM_ID)); + } + return NPERR_NO_ERROR; +} + +NPError PluginGetURLTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + if (test_completed()) { + return PluginTest::NewStream(type, stream, seekable, stype); + } + + if (!referrer_target_url_.empty()) { + return NPERR_NO_ERROR; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + + if (expect_404_response_) { + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + if (!window_obj) { + SetError("Failed to get NPObject for plugin instance2"); + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + std::string script = "javascript:alert('Hi there from plugin');"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + NPVariant result_var; + + npn_evaluate_context_ = true; + HostFunctions()->evaluate(id(), window_obj, &script_string, &result_var); + npn_evaluate_context_ = false; + return NPERR_NO_ERROR; + } + + if (!fail_write_url_.empty()) { + return NPERR_NO_ERROR; + } + + + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + + switch (stream_id) { + case SELF_URL_STREAM_ID: + break; + case FETCHED_URL_STREAM_ID: + { + std::string filename = self_url_; + if (filename.find("file:///", 0) != 0) { + SetError("Test expects a file-url."); + break; + } + + // TODO(evanm): use the net:: functions to convert file:// URLs to + // on-disk file paths. But it probably doesn't actually matter in + // this test. + +#if defined(OS_WIN) + filename = filename.substr(8); // remove "file:///" + // Assume an ASCII path on Windows. + FilePath path = FilePath(ASCIIToWide(filename)); +#else + filename = filename.substr(7); // remove "file://" + FilePath path = FilePath(filename); +#endif + + test_file_ = file_util::OpenFile(path, "r"); + if (!test_file_) { + SetError("Could not open source file"); + } + } + break; + case BOGUS_URL_STREAM_ID: + SetError("Unexpected NewStream for BOGUS_URL"); + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +int32 PluginGetURLTest::WriteReady(NPStream *stream) { + if (test_completed()) { + return PluginTest::WriteReady(stream); + } + + if (!referrer_target_url_.empty()) { + return STREAM_CHUNK; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + if (stream_id == BOGUS_URL_STREAM_ID) + SetError("Received WriteReady for BOGUS_URL"); + + return STREAM_CHUNK; +} + +int32 PluginGetURLTest::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + if (test_completed()) { + return PluginTest::Write(stream, offset, len, buffer); + } + + if (!fail_write_url_.empty()) { + SignalTestCompleted(); + return -1; + } + + if (!referrer_target_url_.empty()) { + return len; + } + + if (stream == NULL) { + SetError("Write got null stream"); + return -1; + } + if (len < 0 || len > STREAM_CHUNK) { + SetError("Write got bogus stream chunk size"); + return -1; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>( + stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + self_url_.append(static_cast<char*>(buffer), len); + break; + case FETCHED_URL_STREAM_ID: + { + char read_buffer[STREAM_CHUNK]; + int32 bytes = fread(read_buffer, 1, len, test_file_); + // Technically, fread could return fewer than len + // bytes. But this is not likely. + if (bytes != len) + SetError("Did not read correct bytelength from source file"); + if (memcmp(read_buffer, buffer, len)) + SetError("Content mismatch between data and source!"); + } + break; + case BOGUS_URL_STREAM_ID: + SetError("Unexpected write callback for BOGUS_URL"); + break; + default: + SetError("Unexpected write callback"); + break; + } + // Pretend that we took all the data. + return len; +} + + +NPError PluginGetURLTest::DestroyStream(NPStream *stream, NPError reason) { + if (test_completed()) { + return PluginTest::DestroyStream(stream, reason); + } + + if (stream == NULL) { + SetError("NewStream got null stream"); + return NPERR_INVALID_PARAM; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + + if (expect_404_response_) { + if (npn_evaluate_context_) { + SetError("Received destroyStream in the context of NPN_Evaluate."); + } + + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + if (!referrer_target_url_.empty()) { + return NPERR_NO_ERROR; + } + + unsigned long stream_id = + reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + case FETCHED_URL_STREAM_ID: + { + char read_buffer[STREAM_CHUNK]; + size_t bytes = fread(read_buffer, 1, sizeof(read_buffer), test_file_); + if (bytes != 0) + SetError("Data and source mismatch on length"); + file_util::CloseFile(test_file_); + } + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + return NPERR_NO_ERROR; +} + +void PluginGetURLTest::StreamAsFile(NPStream* stream, const char* fname) { + if (stream == NULL) { + SetError("NewStream got null stream"); + return; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(stream->notifyData), + cast_validity_check); + unsigned long stream_id = + reinterpret_cast<unsigned long>(stream->notifyData); + switch (stream_id) { + case SELF_URL_STREAM_ID: + // don't care + break; + default: + SetError("Unexpected NewStream callback"); + break; + } +} + +void PluginGetURLTest::URLNotify(const char* url, NPReason reason, void* data) { + if (!tests_in_progress_) { + SetError("URLNotify received after tests completed"); + return; + } + + if (!url) { + SetError("URLNotify received NULL url"); + return; + } + + COMPILE_ASSERT(sizeof(unsigned long) <= sizeof(data), cast_validity_check); + unsigned long stream_id = reinterpret_cast<unsigned long>(data); + switch (stream_id) { + case SELF_URL_STREAM_ID: + if (strcmp(url, SELF_URL) != 0) + SetError("URLNotify reported incorrect url for SELF_URL"); + + // We have our stream url. Go fetch it. + HostFunctions()->geturlnotify(id(), self_url_.c_str(), NULL, + reinterpret_cast<void*>(FETCHED_URL_STREAM_ID)); + break; + case FETCHED_URL_STREAM_ID: + if (!url || strcmp(url, self_url_.c_str()) != 0) + SetError("URLNotify reported incorrect url for FETCHED_URL"); + tests_in_progress_--; + break; + case BOGUS_URL_STREAM_ID: + if (reason != NPRES_NETWORK_ERR) { + std::string err = "BOGUS_URL received unexpected URLNotify status: "; + err.append(IntToString(reason)); + SetError(err); + } + tests_in_progress_--; + break; + default: + SetError("Unexpected NewStream callback"); + break; + } + + if (tests_in_progress_ == 0) + SignalTestCompleted(); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_geturl_test.h b/webkit/glue/plugins/test/plugin_geturl_test.h new file mode 100644 index 0000000..9d5b826 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_geturl_test.h @@ -0,0 +1,55 @@ +// Copyright (c) 2006-2008 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 WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ + +#include <stdio.h> + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginGetURLTest test functionality of the NPN_GetURL +// and NPN_GetURLNotify methods. +// +// This test first discovers it's URL by sending a GetURL request +// for 'javascript:top.location'. After receiving that, the +// test will request the url itself (again via GetURL). +class PluginGetURLTest : public PluginTest { + public: + // Constructor. + PluginGetURLTest(NPP id, NPNetscapeFuncs *host_functions); + + // + // NPAPI functions + // + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void StreamAsFile(NPStream* stream, const char* fname); + virtual void URLNotify(const char* url, NPReason reason, void* data); + + private: + bool tests_started_; + int tests_in_progress_; + std::string self_url_; + FILE* test_file_; + bool expect_404_response_; + // This flag is set to true in the context of the NPN_Evaluate call. + bool npn_evaluate_context_; + std::string page_not_found_url_; + std::string fail_write_url_; + std::string referrer_target_url_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_GETURL_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_javascript_open_popup.cc b/webkit/glue/plugins/test/plugin_javascript_open_popup.cc new file mode 100644 index 0000000..0f93bf4 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_javascript_open_popup.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2006-2008 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 "build/build_config.h" +#include "webkit/glue/plugins/test/plugin_javascript_open_popup.h" + +#if defined(USE_X11) +#include "third_party/npapi/bindings/npapi_x11.h" +#endif +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +ExecuteJavascriptOpenPopupWithPluginTest:: + ExecuteJavascriptOpenPopupWithPluginTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + popup_window_test_started_(false) { +} + +int16 ExecuteJavascriptOpenPopupWithPluginTest::SetWindow( + NPWindow* window) { + if (window->window == NULL) + return NPERR_NO_ERROR; + + if (!popup_window_test_started_) { + popup_window_test_started_ = true; + HostFunctions()->geturl( + id(), "popup_window_with_target_plugin.html", "_blank"); + } + return NPERR_NO_ERROR; +} + +// ExecuteJavascriptPopupWindowTargetPluginTest member defines. +ExecuteJavascriptPopupWindowTargetPluginTest:: + ExecuteJavascriptPopupWindowTargetPluginTest( + NPP id, NPNetscapeFuncs* host_functions) + : PluginTest(id, host_functions), + test_completed_(false) { +} + +int16 ExecuteJavascriptPopupWindowTargetPluginTest::SetWindow( + NPWindow* window) { + if (window->window == NULL) + return NPERR_NO_ERROR; + + if (!test_completed_) { + if (CheckWindow(window)) { + SignalTestCompleted(); + test_completed_ = true; + } + } + return PluginTest::SetWindow(window); +} + +#if defined(OS_WIN) +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + HWND window_handle = reinterpret_cast<HWND>(window->window); + + if (IsWindow(window_handle)) { + HWND parent_window = GetParent(window_handle); + if (!IsWindow(parent_window) || parent_window == GetDesktopWindow()) + SetError("Windowed plugin instantiated with NULL parent"); + return true; + } + + return false; +} + +#elif defined(USE_X11) +// This code blindly follows the same sorts of verifications done on +// the Windows side. Does it make sense on X? Maybe not really, but +// it can't hurt to do extra validations. +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + Window xwindow = reinterpret_cast<Window>(window->window); + // Grab a pointer to the extra SetWindow data so we can grab the display out. + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window->ws_info); + + if (xwindow) { + Window root, parent; + Status status = XQueryTree(extra->display, xwindow, &root, &parent, + NULL, NULL); // NULL children info. + DCHECK(status != 0); + if (!parent || parent == root) + SetError("Windowed plugin instantiated with NULL parent"); + return true; + } + + return false; +} +#elif defined(OS_MACOSX) +bool ExecuteJavascriptPopupWindowTargetPluginTest::CheckWindow( + NPWindow* window) { + // TODO(port) scaffolding--replace with a real test once NPWindow is done. + return false; +} +#endif + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_javascript_open_popup.h b/webkit/glue/plugins/test/plugin_javascript_open_popup.h new file mode 100644 index 0000000..552397a --- /dev/null +++ b/webkit/glue/plugins/test/plugin_javascript_open_popup.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests the case where a windowed plugin instance is +// instantiated in a popup window. The plugin instance needs to +// have a valid parent window. +class ExecuteJavascriptOpenPopupWithPluginTest : public PluginTest { + public: + // Constructor. + ExecuteJavascriptOpenPopupWithPluginTest( + NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* window); + + private: + bool popup_window_test_started_; +}; + +// This class represents a windowed plugin instance instantiated within a +// popup window. It verifies that the plugin instance has a valid parent. +class ExecuteJavascriptPopupWindowTargetPluginTest : public PluginTest { + public: + ExecuteJavascriptPopupWindowTargetPluginTest( + NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* window); + + private: + // Do a platform-specific validation of the passed-in |window|. + // E.g. on Windows, verifies window->window is a reasonable HWND. + // Returns true if the test should be marked complete. + bool CheckWindow(NPWindow* window); + + bool test_completed_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_JAVASCRIPT_OPEN_POPUP_H diff --git a/webkit/glue/plugins/test/plugin_new_fails_test.cc b/webkit/glue/plugins/test/plugin_new_fails_test.cc new file mode 100644 index 0000000..2feeec6 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_new_fails_test.cc @@ -0,0 +1,18 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_new_fails_test.h" + +namespace NPAPIClient { + +NewFailsTest::NewFailsTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError NewFailsTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + return NPERR_GENERIC_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_new_fails_test.h b/webkit/glue/plugins/test/plugin_new_fails_test.h new file mode 100644 index 0000000..1acf9e5 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_new_fails_test.h @@ -0,0 +1,21 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NEW_FAILS_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NEW_FAILS_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +class NewFailsTest : public PluginTest { + public: + NewFailsTest(NPP id, NPNetscapeFuncs *host_functions); + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_PLUGIN_NPP_NEW_FAILS_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc new file mode 100644 index 0000000..b62a764 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_npobject_lifetime_test.h" + +namespace NPAPIClient { + +const int kNPObjectLifetimeTimer = 100; +const int kNPObjectLifetimeTimerElapse = 2000; + +NPObject* NPObjectLifetimeTestInstance2::plugin_instance_object_ = NULL; + +NPObjectDeletePluginInNPN_Evaluate* + NPObjectDeletePluginInNPN_Evaluate::g_npn_evaluate_test_instance_ = NULL; + +NPObjectLifetimeTest::NPObjectLifetimeTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + other_plugin_instance_object_(NULL) { +} + +NPError NPObjectLifetimeTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + HWND window_handle = reinterpret_cast<HWND>(pNPWindow->window); + if (!::GetProp(window_handle, L"Plugin_Instance")) { + ::SetProp(window_handle, L"Plugin_Instance", this); + // We attempt to retreive the NPObject for the plugin instance identified + // by the NPObjectLifetimeTestInstance2 class as it may not have been + // instantiated yet. + SetTimer(window_handle, kNPObjectLifetimeTimer, kNPObjectLifetimeTimerElapse, + TimerProc); + } + return NPERR_NO_ERROR; +} + +void CALLBACK NPObjectLifetimeTest::TimerProc( + HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds) { + + KillTimer(window, kNPObjectLifetimeTimer); + NPObjectLifetimeTest* this_instance = + reinterpret_cast<NPObjectLifetimeTest*> + (::GetProp(window, L"Plugin_Instance")); + + this_instance->other_plugin_instance_object_ = + NPObjectLifetimeTestInstance2::plugin_instance_object_; + this_instance->HostFunctions()->retainobject( + this_instance->other_plugin_instance_object_); + + NPString script; + script.UTF8Characters = "javascript:DeleteSecondPluginInstance()"; + script.UTF8Length = static_cast<uint32_t>(strlen(script.UTF8Characters)); + + this_instance->HostFunctions()->geturlnotify( + this_instance->id(), "javascript:DeleteSecondPluginInstance()", NULL, + reinterpret_cast<void*>(1)); +} + +void NPObjectLifetimeTest::URLNotify(const char* url, NPReason reason, + void* data) { + // Create a "location" identifier. + NPIdentifier identifier = HostFunctions()->getstringidentifier("location"); + // Declare a local variant value. + NPVariant variantValue; + // Get the location property from the window object (which is another object). + bool b1 = HostFunctions()->getproperty(id(), other_plugin_instance_object_, + identifier, &variantValue ); + HostFunctions()->releaseobject(other_plugin_instance_object_); + other_plugin_instance_object_ = NULL; + // If this test failed, then we'd have crashed by now. + SignalTestCompleted(); +} + +NPObjectLifetimeTestInstance2::NPObjectLifetimeTestInstance2( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPObjectLifetimeTestInstance2::~NPObjectLifetimeTestInstance2() { + if (plugin_instance_object_) { + HostFunctions()->releaseobject(plugin_instance_object_); + plugin_instance_object_ = NULL; + } +} + +NPError NPObjectLifetimeTestInstance2::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (!plugin_instance_object_) { + if (!HostFunctions()->getvalue(id(), NPNVWindowNPObject, + &plugin_instance_object_)) { + SetError("Failed to get NPObject for plugin instance2"); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + } + + return NPERR_NO_ERROR; +} + + +NPObjectDeletePluginInNPN_Evaluate::NPObjectDeletePluginInNPN_Evaluate( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + plugin_instance_object_(NULL), + npn_evaluate_timer_proc_set_(false) { + g_npn_evaluate_test_instance_ = this; +} + +NPObjectDeletePluginInNPN_Evaluate::~NPObjectDeletePluginInNPN_Evaluate() { + if (plugin_instance_object_) { + HostFunctions()->releaseobject(plugin_instance_object_); + plugin_instance_object_ = NULL; + } +} + +NPError NPObjectDeletePluginInNPN_Evaluate::SetWindow(NPWindow* np_window) { + if (np_window->window == NULL) + return NPERR_NO_ERROR; + + HWND window_handle = reinterpret_cast<HWND>(np_window->window); + // We setup a timerproc to invoke NPN_Evaluate to destroy this plugin + // instance. This is to ensure that we don't destroy the plugin instance + // while it is being used in webkit as this leads to crashes and is a + // more accurate representation of the renderer crash as described in + // http://b/issue?id=1134683. + if (!npn_evaluate_timer_proc_set_) { + npn_evaluate_timer_proc_set_ = true; + SetTimer(window_handle, kNPObjectLifetimeTimer, kNPObjectLifetimeTimerElapse, + TimerProc); + } + + return NPERR_NO_ERROR; +} + +void CALLBACK NPObjectDeletePluginInNPN_Evaluate::TimerProc( + HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds) { + + KillTimer(window, kNPObjectLifetimeTimer); + NPObject *window_obj = NULL; + g_npn_evaluate_test_instance_->HostFunctions()->getvalue( + g_npn_evaluate_test_instance_->id(), NPNVWindowNPObject, + &window_obj); + + if (!window_obj) { + g_npn_evaluate_test_instance_->SetError( + "Failed to get NPObject for plugin instance2"); + g_npn_evaluate_test_instance_->SignalTestCompleted(); + return; + } + + std::string script = "javascript:DeletePluginWithinScript()"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = + static_cast<unsigned int>(script.length()); + + NPVariant result_var; + bool result = g_npn_evaluate_test_instance_->HostFunctions()->evaluate( + g_npn_evaluate_test_instance_->id(), window_obj, + &script_string, &result_var); + // If this test failed we would have crashed by now. +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h new file mode 100644 index 0000000..4303d99 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_lifetime_test.h @@ -0,0 +1,81 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ + +#include "build/build_config.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The NPObjectLifeTime class tests the case where a plugin has an NPObject +// which points to a different plugin instance on a different frame in the +// page and whether refcounts on this npobject are valid when the source frame +// is destroyed. +class NPObjectLifetimeTest : public PluginTest { + public: + // Constructor. + NPObjectLifetimeTest(NPP id, NPNetscapeFuncs *host_functions); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + + virtual void URLNotify(const char* url, NPReason reason, void* data); + + protected: + NPObject* other_plugin_instance_object_; + +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds); +#endif + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectLifetimeTest); +}; + +// The NPObjectLifetimeTestInstance2 class represents the plugin instance +// which is deleted by the NPObjectLifeTime class via a javascript function. +class NPObjectLifetimeTestInstance2 : public PluginTest { + public: + // Constructor. + NPObjectLifetimeTestInstance2(NPP id, NPNetscapeFuncs *host_functions); + ~NPObjectLifetimeTestInstance2(); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + protected: + static NPObject* plugin_instance_object_; + friend class NPObjectLifetimeTest; + + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectLifetimeTestInstance2); +}; + +// The NPObjectLifeTime class tests the case where a plugin instance is +// destroyed in NPN_Evaluate +class NPObjectDeletePluginInNPN_Evaluate : public PluginTest { + public: + // Constructor. + NPObjectDeletePluginInNPN_Evaluate(NPP id, NPNetscapeFuncs *host_functions); + ~NPObjectDeletePluginInNPN_Evaluate(); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); + + protected: + NPObject* plugin_instance_object_; +#if defined(OS_WIN) + static void CALLBACK TimerProc(HWND window, UINT message, UINT timer_id, + unsigned long elapsed_milli_seconds); +#endif + + private: + bool npn_evaluate_timer_proc_set_; + static NPObjectDeletePluginInNPN_Evaluate* g_npn_evaluate_test_instance_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(NPObjectDeletePluginInNPN_Evaluate); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_LIFETIME_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc b/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc new file mode 100644 index 0000000..5b3a2ca --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_proxy_test.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2006-2008 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/basictypes.h" +#include "base/compiler_specific.h" + +#if defined(OS_WIN) +#define STRSAFE_NO_DEPRECATE +#include <strsafe.h> +#endif +#include "webkit/glue/plugins/test/plugin_npobject_proxy_test.h" + +namespace NPAPIClient { + +NPObjectProxyTest::NPObjectProxyTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError NPObjectProxyTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + NPIdentifier document_id = HostFunctions()->getstringidentifier("document"); + NPIdentifier create_text_node_id = HostFunctions()->getstringidentifier("createTextNode"); + NPIdentifier append_child_id = HostFunctions()->getstringidentifier("appendChild"); + + NPVariant docv; + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + HostFunctions()->getproperty(id(), window_obj, document_id, &docv); + NPObject *doc = NPVARIANT_TO_OBJECT(docv); + + NPVariant strv; + MSVC_SUPPRESS_WARNING(4267); + STRINGZ_TO_NPVARIANT("div", strv); + + NPVariant textv; + HostFunctions()->invoke(id(), doc, create_text_node_id, &strv, 1, &textv); + + NPVariant v; + HostFunctions()->invoke(id(), doc, append_child_id, &textv, 1, &v); + + // If this test failed, then we'd have crashed by now. + SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_npobject_proxy_test.h b/webkit/glue/plugins/test/plugin_npobject_proxy_test.h new file mode 100644 index 0000000..3d14ddb --- /dev/null +++ b/webkit/glue/plugins/test/plugin_npobject_proxy_test.h @@ -0,0 +1,27 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The NPObjectProxyTest tests that when we proxy an NPObject that is itself +// a proxy, we don't create a new proxy but instead just use the original +// pointer. + +class NPObjectProxyTest : public PluginTest { + public: + // Constructor. + NPObjectProxyTest(NPP id, NPNetscapeFuncs *host_functions); + + // NPAPI SetWindow handler. + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_NPOBJECT_PROXY_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_private_test.cc b/webkit/glue/plugins/test/plugin_private_test.cc new file mode 100644 index 0000000..cdab7ce --- /dev/null +++ b/webkit/glue/plugins/test/plugin_private_test.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/test/plugin_private_test.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +PrivateTest::PrivateTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PrivateTest::New(uint16 mode, int16 argc, + const char* argn[], const char* argv[], + NPSavedData* saved) { + PluginTest::New(mode, argc, argn, argv, saved); + + NPBool private_mode = 0; + NPNetscapeFuncs* browser = NPAPIClient::PluginClient::HostFunctions(); + NPError result = browser->getvalue( + id(), NPNVprivateModeBool, &private_mode); + if (result != NPERR_NO_ERROR) { + SetError("Failed to read NPNVprivateModeBool value."); + } else { + NPIdentifier location = HostFunctions()->getstringidentifier("location"); + NPIdentifier href = HostFunctions()->getstringidentifier("href"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant location_var; + HostFunctions()->getproperty(id(), window_obj, location, &location_var); + + NPVariant href_var; + HostFunctions()->getproperty(id(), NPVARIANT_TO_OBJECT(location_var), href, + &href_var); + std::string href_str(href_var.value.stringValue.UTF8Characters, + href_var.value.stringValue.UTF8Length); + bool private_expected = href_str.find("?private") != href_str.npos; + if (private_mode != static_cast<NPBool>(private_expected)) + SetError("NPNVprivateModeBool returned incorrect value."); + + HostFunctions()->releasevariantvalue(&href_var); + HostFunctions()->releasevariantvalue(&location_var); + HostFunctions()->releaseobject(window_obj); + } + + SignalTestCompleted(); + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_private_test.h b/webkit/glue/plugins/test/plugin_private_test.h new file mode 100644 index 0000000..db6b5d1 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_private_test.h @@ -0,0 +1,25 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// The PluginPrivateTest tests that a plugin can query if the browser is in +// private browsing mode. +class PrivateTest : public PluginTest { + public: + PrivateTest(NPP id, NPNetscapeFuncs *host_functions); + + // Initialize this PluginTest based on the arguments from NPP_New. + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_PRIVATE_TEST_H_ diff --git a/webkit/glue/plugins/test/plugin_schedule_timer_test.cc b/webkit/glue/plugins/test/plugin_schedule_timer_test.cc new file mode 100644 index 0000000..fbfce34 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_schedule_timer_test.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_schedule_timer_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +using base::Time; + +namespace NPAPIClient { + +// The times below are accurate but they are not tested against because it +// might make the test flakey. +ScheduleTimerTest::Event + ScheduleTimerTest::schedule_[ScheduleTimerTest::kNumEvents] = { + { 0, -1, 0, 100, false, -1 }, // schedule 0 100ms no-repeat + { 100, 0, 0, 200, false, -1 }, // schedule 0 200ms no-repeat + { 300, 0, 0, 100, true, -1 }, // schedule 0 100ms repeat + { 400, 0, 1, 50, true, -1 }, // schedule 1 50ms repeat + { 450, 1, -1, 0, true, -1 }, // receive 1 repeating + { 500, 0, -1, 0, true, -1 }, // receive 0 repeating + { 500, 1, -1, 0, true, -1 }, // receive 1 repeating + { 550, 1, -1, 0, true, -1 }, // receive 1 repeating + { 600, 0, -1, 0, true, 0 }, // receive 0 repeating and unschedule + { 600, 1, 2, 400, true, 1 }, // receive 1 repeating and unschedule + { 1000, 2, -1, 0, true, 2 }, // receive final and unschedule +}; + +ScheduleTimerTest::ScheduleTimerTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + num_received_events_(0) { + for (int i = 0; i < kNumTimers; ++i) { + timer_ids_[i] = 0; + } + for (int i = 0; i < kNumEvents; ++i) { + received_events_[i] = false; + } +} + +NPError ScheduleTimerTest::New( + uint16 mode, int16 argc, const char* argn[], const char* argv[], + NPSavedData* saved) { + NPError error = PluginTest::New(mode, argc, argn, argv, saved); + if (error != NPERR_NO_ERROR) + return error; + + start_time_ = Time::Now(); + HandleEvent(0); + + return NPERR_NO_ERROR; +} + +void ScheduleTimerTest::OnTimer(uint32 timer_id) { + Time current_time = Time::Now(); + int relative_time = static_cast<int>( + (current_time - start_time_).InMilliseconds()); + + // See if there is a matching unreceived event. + int event_index = FindUnreceivedEvent(relative_time, timer_id); + if (event_index < 0) { + SetError("Received unexpected timer event"); + SignalTestCompleted(); + return; + } + + HandleEvent(event_index); + + // Finish test if all events have happened. + if (num_received_events_ == kNumEvents) + SignalTestCompleted(); +} + +int ScheduleTimerTest::FindUnreceivedEvent(int time, uint32 timer_id) { + for (int i = 0; i < kNumEvents; ++i) { + const Event& event = schedule_[i]; + if (!received_events_[i] && + timer_ids_[event.received_index] == timer_id) { + return i; + } + } + return -1; +} + +namespace { +void OnTimerHelper(NPP id, uint32 timer_id) { + ScheduleTimerTest* plugin_object = + static_cast<ScheduleTimerTest*>(id->pdata); + if (plugin_object) { + plugin_object->OnTimer(timer_id); + } +} +} + +void ScheduleTimerTest::HandleEvent(int event_index) { + const Event& event = schedule_[event_index]; + + // Mark event as received. + DCHECK(!received_events_[event_index]); + received_events_[event_index] = true; + ++num_received_events_; + + // Unschedule timer if present. + if (event.unscheduled_index >= 0) { + HostFunctions()->unscheduletimer( + id(), timer_ids_[event.unscheduled_index]); + } + + // Schedule timer if present. + if (event.scheduled_index >= 0) { + timer_ids_[event.scheduled_index] = HostFunctions()->scheduletimer( + id(), event.scheduled_interval, event.schedule_repeated, OnTimerHelper); + } +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_schedule_timer_test.h b/webkit/glue/plugins/test/plugin_schedule_timer_test.h new file mode 100644 index 0000000..9ca947f --- /dev/null +++ b/webkit/glue/plugins/test/plugin_schedule_timer_test.h @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H + +#include <vector> + +#include "base/at_exit.h" +#include "base/time.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests scheduling and unscheduling of timers using +// NPN_ScheduleTimer and NPN_UnscheduleTimer. +class ScheduleTimerTest : public PluginTest { + public: + ScheduleTimerTest(NPP id, NPNetscapeFuncs *host_functions); + + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + void OnTimer(uint32 timer_id); + + private: + // base::Time needs one of these. + base::AtExitManager at_exit_manager_; + + // Table mapping timer index (as used in event schedule) to timer id. + static const int kNumTimers = 3; + uint32 timer_ids_[kNumTimers]; + + // Schedule of events for test. + static const int kNumEvents = 11; + struct Event { + int time; + + // The index of the timer that triggered the event or -1 for the first + // event. + int received_index; + + // The index of the timer to schedule on this event or -1. + int scheduled_index; + + // Info about the timer to be scheduled (if any). + uint32 scheduled_interval; + bool schedule_repeated; + + // The index of the timer to unschedule on this event or -1. + int unscheduled_index; + }; + static Event schedule_[kNumEvents]; + int num_received_events_; + + // Set of events that have been received (by index). + bool received_events_[kNumEvents]; + + // Time of initial event. + base::Time start_time_; + + // Returns index of matching unreceived event or -1 if not found. + int FindUnreceivedEvent(int time, uint32 timer_id); + void HandleEvent(int event_index); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_SCHEDULE_TIMER_TEST_H diff --git a/webkit/glue/plugins/test/plugin_test.cc b/webkit/glue/plugins/test/plugin_test.cc new file mode 100644 index 0000000..141c91a --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_test.h" + +#include "base/string_util.h" +#include "webkit/glue/plugins/test/npapi_constants.h" + +namespace NPAPIClient { + +PluginTest::PluginTest(NPP id, NPNetscapeFuncs *host_functions) { + id_ = id; + id_->pdata = this; + host_functions_ = host_functions; + test_completed_ = false; +} + +NPError PluginTest::New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved) { + test_name_ = this->GetArgValue("name", argc, argn, argv); + test_id_ = this->GetArgValue("id", argc, argn, argv); + return NPERR_NO_ERROR; +} + +NPError PluginTest::Destroy() { + return NPERR_NO_ERROR; +} + +NPError PluginTest::SetWindow(NPWindow* pNPWindow) { + return NPERR_NO_ERROR; +} + +// It's a shame I have to implement URLEncode. But, using webkit's +// or using chrome's means a ball of string of dlls and dependencies that +// is very very long. After spending far too much time on it, +// I'll just encode it myself. Too bad Microsoft doesn't implement +// this in a reusable way either. Both webkit and chrome will +// end up using libicu, which is a string of dependencies we don't +// want. + +inline unsigned char toHex(const unsigned char x) { + return x > 9 ? (x + 'A' - 10) : (x + '0'); +} + +std::string URLEncode(const std::string &sIn) { + std::string sOut; + + const size_t length = sIn.length(); + for (size_t idx = 0; idx < length;) { + const char ch = sIn.at(idx); + if (isalnum(ch)) { + sOut.append(1, ch); + } else if (isspace(ch) && ((ch != '\n') && (ch != '\r'))) { + sOut.append(1, '+'); + } else { + sOut.append(1, '%'); + sOut.append(1, toHex(ch>>4)); + sOut.append(1, toHex(ch%16)); + } + idx++; + } + return sOut; +} + +void PluginTest::SignalTestCompleted() { + NPObject *window_obj = NULL; + host_functions_->getvalue(id_, NPNVWindowNPObject, &window_obj); + if (!window_obj) + return; + + test_completed_ = true; + // To signal test completion, we expect a couple of + // javascript functions to be defined in the webpage + // which hosts this plugin: + // onSuccess(test_name, test_id) + // onFailure(test_name, test_id, error_message) + std::string script("javascript:"); + if (Succeeded()) { + script.append("onSuccess(\""); + script.append(test_name_); + script.append("\",\""); + script.append(test_id_); + script.append("\");"); + } else { + script.append("onFailure(\""); + script.append(test_name_); + script.append("\",\""); + script.append(test_id_); + script.append("\",\""); + script.append(test_status_); + script.append("\");"); + } + + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = static_cast<unsigned int>(script.length()); + + NPVariant result_var; + host_functions_->evaluate(id_, window_obj, &script_string, &result_var); +} + +const char *PluginTest::GetArgValue(const char *name, const int16 argc, + const char *argn[], const char *argv[]) { + for (int idx = 0; idx < argc; idx++) + if (base::strcasecmp(argn[idx], name) == 0) + return argv[idx]; + return NULL; +} + +void PluginTest::SetError(const std::string &msg) { + test_status_.append(msg); +} + +NPError PluginTest::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype) { + // There is no default action here. + return NPERR_NO_ERROR; +} + +int32 PluginTest::WriteReady(NPStream *stream) { + // Take data in small chunks + return 4096; +} + +int32 PluginTest::Write(NPStream *stream, int32 offset, int32 len, + void *buffer) { + // Pretend that we took all the data. + return len; +} + +NPError PluginTest::DestroyStream(NPStream *stream, NPError reason) { + // There is no default action. + return NPERR_NO_ERROR; +} + +void PluginTest::StreamAsFile(NPStream* stream, const char* fname) { + // There is no default action. +} + +void PluginTest::URLNotify(const char* url, NPReason reason, void* data) { + // There is no default action +} + +int16 PluginTest::HandleEvent(void* event) { + // There is no default action + return 0; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_test.h b/webkit/glue/plugins/test/plugin_test.h new file mode 100644 index 0000000..f06307e --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test.h @@ -0,0 +1,131 @@ +// Copyright (c) 2006-2008 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 WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H__ + +#include <string> + +#include "base/string_util.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +// A PluginTest represents an instance of the plugin, which in +// our case is a test case. +class PluginTest { + public: + // Constructor. + PluginTest(NPP id, NPNetscapeFuncs *host_functions); + + // Destructor + virtual ~PluginTest() {} + + // Returns true if the test runs in windowless plugin mode. + virtual bool IsWindowless() const { return false; } + + // + // NPAPI Functions + // + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + virtual NPError Destroy(); + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stype); + virtual int32 WriteReady(NPStream *stream); + virtual int32 Write(NPStream *stream, int32 offset, int32 len, + void *buffer); + virtual NPError DestroyStream(NPStream *stream, NPError reason); + virtual void StreamAsFile(NPStream* stream, const char* fname); + virtual void URLNotify(const char* url, NPReason reason, void* data); + virtual int16 HandleEvent(void* event); + // Returns true if the test has not had any errors. + bool Succeeded() { return test_status_.length() == 0; } + + // Sets an error for the test case. Appends the msg to the + // error that will be returned from the test. + void SetError(const std::string &msg); + + // Expect two string values are equal, and if not, logs an + // appropriate error about it. + void ExpectStringLowerCaseEqual(const std::string &val1, const std::string &val2) { + if (!LowerCaseEqualsASCII(val1, val2.c_str())) { + std::string err; + err = "Expected Equal for '"; + err.append(val1); + err.append("' and '"); + err.append(val2); + err.append("'"); + SetError(err); + } + }; + + // Expect two values to not be equal, and if they are + // logs an appropriate error about it. + void ExpectAsciiStringNotEqual(const char *val1, const char *val2) { + if (val1 == val2) { + std::string err; + err = "Expected Not Equal for '"; + err.append(val1); + err.append("' and '"); + err.append(val2); + err.append("'"); + SetError(err); + } + } + // Expect two integer values are equal, and if not, logs an + // appropriate error about it. + void ExpectIntegerEqual(int val1, int val2) { + if (val1 != val2) { + std::string err; + err = "Expected Equal for '"; + err.append(IntToString(val1)); + err.append("' and '"); + err.append(IntToString(val2)); + err.append("'"); + SetError(err); + } + } + + + protected: + // Signals to the Test that invoked us that the test is + // completed. This is done by forcing the plugin to + // set a cookie in the browser window, which the test program + // is waiting for. Note - because this is done by + // using javascript, the browser must have the frame + // setup before the plugin calls this function. So plugin + // tests MUST NOT call this function prior to having + // received the SetWindow() callback from the browser. + void SignalTestCompleted(); + + // Helper function to lookup names in the input array. + // If the name is found, returns the value, otherwise + // returns NULL. + const char *GetArgValue(const char *name, const int16 argc, + const char *argn[], const char *argv[]); + + // Access to the list of functions provided + // by the NPAPI host. + NPNetscapeFuncs *HostFunctions() { return host_functions_; } + + // The NPP Identifier for this plugin instance. + NPP id() { return id_; } + std::string test_id() const { return test_id_; } + std::string test_name() const { return test_name_; } + bool test_completed() const { return test_completed_; } + private: + NPP id_; + NPNetscapeFuncs * host_functions_; + std::string test_name_; + std::string test_id_; + std::string test_status_; + bool test_completed_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_H__ diff --git a/webkit/glue/plugins/test/plugin_test_factory.cc b/webkit/glue/plugins/test/plugin_test_factory.cc new file mode 100644 index 0000000..e2b42b3 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test_factory.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/test/plugin_test_factory.h" + +#include "webkit/glue/plugins/test/plugin_arguments_test.h" +#include "webkit/glue/plugins/test/plugin_delete_plugin_in_stream_test.h" +#include "webkit/glue/plugins/test/plugin_get_javascript_url_test.h" +#include "webkit/glue/plugins/test/plugin_get_javascript_url2_test.h" +#include "webkit/glue/plugins/test/plugin_geturl_test.h" +#include "webkit/glue/plugins/test/plugin_javascript_open_popup.h" +#include "webkit/glue/plugins/test/plugin_new_fails_test.h" +#include "webkit/glue/plugins/test/plugin_npobject_lifetime_test.h" +#include "webkit/glue/plugins/test/plugin_npobject_proxy_test.h" +#include "webkit/glue/plugins/test/plugin_private_test.h" +#include "webkit/glue/plugins/test/plugin_schedule_timer_test.h" +#include "webkit/glue/plugins/test/plugin_thread_async_call_test.h" +#include "webkit/glue/plugins/test/plugin_window_size_test.h" +#if defined(OS_WIN) +#include "webkit/glue/plugins/test/plugin_windowed_test.h" +#endif +#include "webkit/glue/plugins/test/plugin_windowless_test.h" + +namespace NPAPIClient { + +PluginTest* CreatePluginTest(const std::string& test_name, + NPP instance, + NPNetscapeFuncs* host_functions) { + PluginTest* new_test = NULL; + + if (test_name == "arguments") { + new_test = new PluginArgumentsTest(instance, host_functions); + } else if (test_name == "geturl" || test_name == "geturl_404_response" || + test_name == "geturl_fail_write" || + test_name == "plugin_referrer_test") { + new_test = new PluginGetURLTest(instance, host_functions); + } else if (test_name == "npobject_proxy") { + new_test = new NPObjectProxyTest(instance, host_functions); +#if defined(OS_WIN) || defined(OS_MACOSX) + // TODO(port): plugin_windowless_test.*. + } else if (test_name == "execute_script_delete_in_paint" || + test_name == "execute_script_delete_in_mouse_move" || + test_name == "delete_frame_test" || + test_name == "multiple_instances_sync_calls" || + test_name == "no_hang_if_init_crashes" || + test_name == "convert_point") { + new_test = new WindowlessPluginTest(instance, host_functions); +#endif + } else if (test_name == "getjavascripturl") { + new_test = new ExecuteGetJavascriptUrlTest(instance, host_functions); + } else if (test_name == "getjavascripturl2") { + new_test = new ExecuteGetJavascriptUrl2Test(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_window_size_test.*. + } else if (test_name == "checkwindowrect") { + new_test = new PluginWindowSizeTest(instance, host_functions); +#endif + } else if (test_name == "self_delete_plugin_stream") { + new_test = new DeletePluginInStreamTest(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_npobject_lifetime_test.*. + } else if (test_name == "npobject_lifetime_test") { + new_test = new NPObjectLifetimeTest(instance, host_functions); + } else if (test_name == "npobject_lifetime_test_second_instance") { + new_test = new NPObjectLifetimeTestInstance2(instance, host_functions); + } else if (test_name == "new_fails") { + new_test = new NewFailsTest(instance, host_functions); + } else if (test_name == "npobject_delete_plugin_in_evaluate") { + new_test = new NPObjectDeletePluginInNPN_Evaluate(instance, host_functions); +#endif + } else if (test_name == "plugin_javascript_open_popup_with_plugin") { + new_test = new ExecuteJavascriptOpenPopupWithPluginTest( + instance, host_functions); + } else if (test_name == "plugin_popup_with_plugin_target") { + new_test = new ExecuteJavascriptPopupWindowTargetPluginTest( + instance, host_functions); + } else if (test_name == "plugin_thread_async_call") { + new_test = new PluginThreadAsyncCallTest(instance, host_functions); + } else if (test_name == "private") { + new_test = new PrivateTest(instance, host_functions); + } else if (test_name == "schedule_timer") { + new_test = new ScheduleTimerTest(instance, host_functions); +#if defined(OS_WIN) + // TODO(port): plugin_windowed_test.*. + } else if (test_name == "hidden_plugin" || + test_name == "create_instance_in_paint" || + test_name == "alert_in_window_message" || + test_name == "ensure_scripting_works_in_destroy") { + new_test = new WindowedPluginTest(instance, host_functions); +#endif + } + + return new_test; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_test_factory.h b/webkit/glue/plugins/test/plugin_test_factory.h new file mode 100644 index 0000000..3fd38d5 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_test_factory.h @@ -0,0 +1,22 @@ +// Copyright (c) 2010 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 WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ +#define WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ + +#include <string> + +#include "third_party/npapi/bindings/nphostapi.h" + +namespace NPAPIClient { + +class PluginTest; + +extern PluginTest* CreatePluginTest(const std::string& test_name, + NPP instance, + NPNetscapeFuncs* host_functions); + +} // namespace NPAPIClient + +#endif // WEBKIT_PORT_PLUGINS_TEST_PLUGIN_TEST_FACTROY_H__ diff --git a/webkit/glue/plugins/test/plugin_thread_async_call_test.cc b/webkit/glue/plugins/test/plugin_thread_async_call_test.cc new file mode 100644 index 0000000..2e9f9e9 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_thread_async_call_test.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/test/plugin_thread_async_call_test.h" + +#include "base/message_loop.h" +#include "base/thread.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +namespace { + +// There are two plugin instances in this test. The long lived instance is used +// for reporting errors and signalling test completion. The short lived one is +// used to verify that async callbacks are not invoked after NPP_Destroy. +PluginThreadAsyncCallTest* g_short_lived_instance; +PluginThreadAsyncCallTest* g_long_lived_instance; + +void OnCallSucceededHelper(void* data) { + static_cast<PluginThreadAsyncCallTest*>(data)->OnCallSucceeded(); +} + +class AsyncCallTask : public Task { + public: + AsyncCallTask(PluginThreadAsyncCallTest* test_class) + : test_class_(test_class) {} + + void Run() { + test_class_->AsyncCall(); + } + + private: + PluginThreadAsyncCallTest* test_class_; +}; + +void OnCallFailed(void* data) { + g_long_lived_instance->SetError("Async callback invoked after NPP_Destroy"); +} + +void OnCallCompletedHelper(void* data) { + static_cast<PluginThreadAsyncCallTest*>(data)->OnCallCompleted(); +} +} + +PluginThreadAsyncCallTest::PluginThreadAsyncCallTest( + NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginThreadAsyncCallTest::New( + uint16 mode, int16 argc, const char* argn[], const char* argv[], + NPSavedData* saved) { + NPError error = PluginTest::New(mode, argc, argn, argv, saved); + if (error != NPERR_NO_ERROR) + return error; + + // Determine whether this is the short lived instance. + for (int i = 0; i < argc; ++i) { + if (base::strcasecmp(argn[i], "short_lived") == 0) { + if (base::strcasecmp(argv[i], "true") == 0) { + g_short_lived_instance = this; + } else { + g_long_lived_instance = this; + } + } + } + + // Schedule an async call that will succeed. Make sure to call that API from + // a different thread to fully test it. + if (this == g_short_lived_instance) { + at_exit_manager_.reset(new base::AtExitManager()); + base::Thread random_thread("random_thread"); + random_thread.Start(); + random_thread.message_loop()->PostTask(FROM_HERE, new AsyncCallTask(this)); + } + + return NPERR_NO_ERROR; +} + +void PluginThreadAsyncCallTest::AsyncCall() { + HostFunctions()->pluginthreadasynccall(id(), OnCallSucceededHelper, this); +} + +void PluginThreadAsyncCallTest::OnCallSucceeded() { + // Delete the short lived instance. + NPIdentifier delete_id = HostFunctions()->getstringidentifier( + "deleteShortLivedInstance"); + + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject, &window_obj); + + NPVariant result; + HostFunctions()->invoke(id(), window_obj, delete_id, NULL, 0, &result); +} + +NPError PluginThreadAsyncCallTest::Destroy() { + if (this == g_short_lived_instance) { + // Schedule an async call that should not be called. + HostFunctions()->pluginthreadasynccall(id(), OnCallFailed, NULL); + + // Schedule an async call to end the test using the long lived instance. + HostFunctions()->pluginthreadasynccall(g_long_lived_instance->id(), + OnCallCompletedHelper, + g_long_lived_instance); + } + + return NPERR_NO_ERROR; +} + +void PluginThreadAsyncCallTest::OnCallCompleted() { + SignalTestCompleted(); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_thread_async_call_test.h b/webkit/glue/plugins/test/plugin_thread_async_call_test.h new file mode 100644 index 0000000..020e5e1 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_thread_async_call_test.h @@ -0,0 +1,38 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H + +#include <vector> + +#include "base/at_exit.h" +#include "base/scoped_ptr.h" +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests scheduling and unscheduling of async callbacks using +// NPN_PluginThreadAsyncCall. +class PluginThreadAsyncCallTest : public PluginTest { + public: + PluginThreadAsyncCallTest(NPP id, NPNetscapeFuncs *host_functions); + + virtual NPError New(uint16 mode, int16 argc, const char* argn[], + const char* argv[], NPSavedData* saved); + + virtual NPError Destroy(); + + void AsyncCall(); + void OnCallSucceeded(); + void OnCallCompleted(); + + private: + // base::Thread needs one of these. + scoped_ptr<base::AtExitManager> at_exit_manager_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_THREAD_ASYNC_CALL_TEST_H diff --git a/webkit/glue/plugins/test/plugin_window_size_test.cc b/webkit/glue/plugins/test/plugin_window_size_test.cc new file mode 100644 index 0000000..9bfabca --- /dev/null +++ b/webkit/glue/plugins/test/plugin_window_size_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/test/plugin_window_size_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +PluginWindowSizeTest::PluginWindowSizeTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { +} + +NPError PluginWindowSizeTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + HWND window = reinterpret_cast<HWND>(pNPWindow->window); + if (!pNPWindow || !::IsWindow(window)) { + SetError("Invalid arguments passed in"); + return NPERR_INVALID_PARAM; + } + + RECT window_rect = {0}; + window_rect.left = pNPWindow->x; + window_rect.top = pNPWindow->y; + window_rect.right = pNPWindow->width; + window_rect.bottom = pNPWindow->height; + + if (!::IsRectEmpty(&window_rect)) { + RECT client_rect = {0}; + ::GetClientRect(window, &client_rect); + if (::IsRectEmpty(&client_rect)) { + SetError("The client rect of the plugin window is empty. Test failed"); + } + + // Bug 6742: ensure that the coordinates passed in are relative to the + // parent HWND. + POINT origin_from_os; + RECT window_rect_from_os; + ::GetWindowRect(window, &window_rect_from_os); + origin_from_os.x = window_rect_from_os.left; + origin_from_os.y = window_rect_from_os.top; + ::ScreenToClient(GetParent(window), &origin_from_os); + if (origin_from_os.x != pNPWindow->x || origin_from_os.y != pNPWindow->y) + SetError("Wrong position passed in to SetWindow! Test failed"); + + SignalTestCompleted(); + } + + return NPERR_NO_ERROR; +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_window_size_test.h b/webkit/glue/plugins/test/plugin_window_size_test.h new file mode 100644 index 0000000..3650671 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_window_size_test.h @@ -0,0 +1,24 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class tests whether the plugin window has a non zero rect +// on the second SetWindow call. +class PluginWindowSizeTest : public PluginTest { + public: + // Constructor. + PluginWindowSizeTest(NPP id, NPNetscapeFuncs *host_functions); + // NPAPI SetWindow handler + virtual NPError SetWindow(NPWindow* pNPWindow); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOW_SIZE_TEST_H diff --git a/webkit/glue/plugins/test/plugin_windowed_test.cc b/webkit/glue/plugins/test/plugin_windowed_test.cc new file mode 100644 index 0000000..5ae8e30 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowed_test.cc @@ -0,0 +1,139 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/plugins/test/plugin_windowed_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +namespace NPAPIClient { + +WindowedPluginTest::WindowedPluginTest(NPP id, NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions), + window_(NULL), done_(false) { +} + +WindowedPluginTest::~WindowedPluginTest() { + if (window_) + DestroyWindow(window_); +} + +NPError WindowedPluginTest::SetWindow(NPWindow* pNPWindow) { + if (pNPWindow->window == NULL) + return NPERR_NO_ERROR; + + if (test_name() == "create_instance_in_paint" && test_id() == "2") { + SignalTestCompleted(); + return NPERR_NO_ERROR; + } + + if (window_) + return NPERR_NO_ERROR; + + HWND parent = reinterpret_cast<HWND>(pNPWindow->window); + if (!pNPWindow || !::IsWindow(parent)) { + SetError("Invalid arguments passed in"); + return NPERR_INVALID_PARAM; + } + + if ((test_name() == "create_instance_in_paint" && test_id() == "1") || + test_name() == "alert_in_window_message") { + static ATOM window_class = 0; + if (!window_class) { + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = &NPAPIClient::WindowedPluginTest::WindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = L"CreateInstanceInPaintTestWindowClass"; + wcex.hIconSm = 0; + window_class = RegisterClassEx(&wcex); + } + + window_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + MAKEINTATOM(window_class), 0, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE , + 0, 0, 100, 100, parent, 0, GetModuleHandle(NULL), 0); + DCHECK(window_); + ::SetProp(window_, L"Plugin_Instance", this); + } + + return NPERR_NO_ERROR; +} + +NPError WindowedPluginTest::Destroy() { + if (test_name() != "ensure_scripting_works_in_destroy") + return NPERR_NO_ERROR; + + // Bug 23706: ensure that scripting works with no asserts. + NPObject *window_obj = NULL; + HostFunctions()->getvalue(id(), NPNVWindowNPObject,&window_obj); + + if (!window_obj) { + SetError("Failed to get NPObject for plugin instance"); + } else { + std::string script = "javascript:GetMagicNumber()"; + NPString script_string; + script_string.UTF8Characters = script.c_str(); + script_string.UTF8Length = + static_cast<unsigned int>(script.length()); + + NPVariant result_var; + bool result = HostFunctions()->evaluate( + id(), window_obj, &script_string, &result_var); + if (!result || + result_var.type != NPVariantType_Int32 || + result_var.value.intValue != 42) { + SetError("Failed to script during NPP_Destroy"); + } + } + + SignalTestCompleted(); + return NPERR_NO_ERROR; +} + +void WindowedPluginTest::CallJSFunction( + WindowedPluginTest* this_ptr, const char* function) { + NPIdentifier function_id = this_ptr->HostFunctions()->getstringidentifier( + function); + + NPObject *window_obj = NULL; + this_ptr->HostFunctions()->getvalue( + this_ptr->id(), NPNVWindowNPObject, &window_obj); + + NPVariant rv; + this_ptr->HostFunctions()->invoke( + this_ptr->id(), window_obj, function_id, NULL, 0, &rv); +} + +LRESULT CALLBACK WindowedPluginTest::WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam) { + WindowedPluginTest* this_ptr = + reinterpret_cast<WindowedPluginTest*> + (::GetProp(window, L"Plugin_Instance")); + + if (this_ptr && !this_ptr->done_) { + if (this_ptr->test_name() == "create_instance_in_paint" && + message == WM_PAINT) { + this_ptr->done_ = true; + CallJSFunction(this_ptr, "CreateNewInstance"); + } else if (this_ptr->test_name() == "alert_in_window_message" && + message == WM_PAINT) { + this_ptr->done_ = true; + // We call this function twice as we want to display two alerts + // and verify that we don't hang the browser. + CallJSFunction(this_ptr, "CallAlert"); + CallJSFunction(this_ptr, "CallAlert"); + } + } + + return DefWindowProc(window, message, wparam, lparam); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_windowed_test.h b/webkit/glue/plugins/test/plugin_windowed_test.h new file mode 100644 index 0000000..949ea86 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowed_test.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class contains a list of windowed plugin tests. Please add additional +// tests to this class. +class WindowedPluginTest : public PluginTest { + public: + WindowedPluginTest(NPP id, NPNetscapeFuncs *host_functions); + ~WindowedPluginTest(); + + private: + static LRESULT CALLBACK WindowProc( + HWND window, UINT message, WPARAM wparam, LPARAM lparam); + static void CallJSFunction(WindowedPluginTest*, const char*); + + virtual NPError SetWindow(NPWindow* pNPWindow); + virtual NPError Destroy(); + + HWND window_; + bool done_; +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_WINDOWED_TEST_H diff --git a/webkit/glue/plugins/test/plugin_windowless_test.cc b/webkit/glue/plugins/test/plugin_windowless_test.cc new file mode 100644 index 0000000..c47c1d7 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowless_test.cc @@ -0,0 +1,260 @@ +// Copyright (c) 2006-2008 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. + +#define STRSAFE_NO_DEPRECATE +#include "base/string_util.h" +#include "webkit/glue/plugins/test/plugin_windowless_test.h" +#include "webkit/glue/plugins/test/plugin_client.h" + +#if defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> +#endif + +namespace NPAPIClient { + +// Remember the first plugin instance for tests involving multiple instances +WindowlessPluginTest* g_other_instance = NULL; + +WindowlessPluginTest::WindowlessPluginTest(NPP id, + NPNetscapeFuncs *host_functions) + : PluginTest(id, host_functions) { + if (!g_other_instance) + g_other_instance = this; +} + +static bool IsPaintEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_PAINT == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == updateEvt; +#endif +} + +static bool IsMouseMoveEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_MOUSEMOVE == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == nullEvent; +#endif +} + +static bool IsMouseUpEvent(NPEvent* np_event) { +#if defined(OS_WIN) + return WM_LBUTTONUP == np_event->event; +#elif defined(OS_MACOSX) + return np_event->what == mouseUp; +#endif +} + +static bool IsWindowActivationEvent(NPEvent* np_event) { +#if defined(OS_WIN) + NOTIMPLEMENTED(); + return false; +#elif defined(OS_MACOSX) + return np_event->what == activateEvt; +#endif +} + +int16 WindowlessPluginTest::HandleEvent(void* event) { + + NPNetscapeFuncs* browser = NPAPIClient::PluginClient::HostFunctions(); + + NPBool supports_windowless = 0; + NPError result = browser->getvalue(id(), NPNVSupportsWindowless, + &supports_windowless); + if ((result != NPERR_NO_ERROR) || (supports_windowless != TRUE)) { + SetError("Failed to read NPNVSupportsWindowless value"); + SignalTestCompleted(); + return PluginTest::HandleEvent(event); + } + + NPEvent* np_event = reinterpret_cast<NPEvent*>(event); + if (IsPaintEvent(np_event)) { +#if defined(OS_WIN) + HDC paint_dc = reinterpret_cast<HDC>(np_event->wParam); + if (paint_dc == NULL) { + SetError("Invalid Window DC passed to HandleEvent for WM_PAINT"); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + + HRGN clipping_region = CreateRectRgn(0, 0, 0, 0); + if (!GetClipRgn(paint_dc, clipping_region)) { + SetError("No clipping region set in window DC"); + DeleteObject(clipping_region); + SignalTestCompleted(); + return NPERR_GENERIC_ERROR; + } + + DeleteObject(clipping_region); +#endif + + if (test_name() == "execute_script_delete_in_paint") { + ExecuteScriptDeleteInPaint(browser); + } else if (test_name() == "multiple_instances_sync_calls") { + MultipleInstanceSyncCalls(browser); + } +#if OS_MACOSX + } else if (IsWindowActivationEvent(np_event) && + test_name() == "convert_point") { + ConvertPoint(browser); +#endif + } else if (IsMouseMoveEvent(np_event) && + test_name() == "execute_script_delete_in_mouse_move") { + ExecuteScript(browser, id(), "DeletePluginWithinScript();", NULL); + SignalTestCompleted(); + } else if (IsMouseUpEvent(np_event) && + test_name() == "delete_frame_test") { + ExecuteScript( + browser, id(), + "parent.document.getElementById('frame').outerHTML = ''", NULL); + } + // If this test failed, then we'd have crashed by now. + return PluginTest::HandleEvent(event); +} + +NPError WindowlessPluginTest::ExecuteScript(NPNetscapeFuncs* browser, NPP id, + const std::string& script, NPVariant* result) { + std::string script_url = "javascript:"; + script_url += script; + + NPString script_string = { script_url.c_str(), script_url.length() }; + NPObject *window_obj = NULL; + browser->getvalue(id, NPNVWindowNPObject, &window_obj); + + NPVariant unused_result; + if (!result) + result = &unused_result; + + return browser->evaluate(id, window_obj, &script_string, result); +} + +void WindowlessPluginTest::ExecuteScriptDeleteInPaint( + NPNetscapeFuncs* browser) { + const NPUTF8* urlString = "javascript:DeletePluginWithinScript()"; + const NPUTF8* targetString = NULL; + browser->geturl(id(), urlString, targetString); + SignalTestCompleted(); +} + +void WindowlessPluginTest::MultipleInstanceSyncCalls(NPNetscapeFuncs* browser) { + if (this == g_other_instance) + return; + + DCHECK(g_other_instance); + ExecuteScript(browser, g_other_instance->id(), "TestCallback();", NULL); + SignalTestCompleted(); +} + +#if defined(OS_MACOSX) +std::string StringForPoint(int x, int y) { + std::string point_string("("); + point_string.append(IntToString(x)); + point_string.append(", "); + point_string.append(IntToString(y)); + point_string.append(")"); + return point_string; +} +#endif + +void WindowlessPluginTest::ConvertPoint(NPNetscapeFuncs* browser) { +#if defined(OS_MACOSX) + // First, just sanity-test that round trips work. + NPCoordinateSpace spaces[] = { NPCoordinateSpacePlugin, + NPCoordinateSpaceWindow, + NPCoordinateSpaceFlippedWindow, + NPCoordinateSpaceScreen, + NPCoordinateSpaceFlippedScreen }; + for (unsigned int i = 0; i < arraysize(spaces); ++i) { + for (unsigned int j = 0; j < arraysize(spaces); ++j) { + double x, y, round_trip_x, round_trip_y; + if (!(browser->convertpoint(id(), 0, 0, spaces[i], &x, &y, spaces[j])) || + !(browser->convertpoint(id(), x, y, spaces[j], &round_trip_x, + &round_trip_y, spaces[i]))) { + SetError("Conversion failed"); + SignalTestCompleted(); + return; + } + if (i != j && x == 0 && y == 0) { + SetError("Converting a coordinate should change it"); + SignalTestCompleted(); + return; + } + if (round_trip_x != 0 || round_trip_y != 0) { + SetError("Round-trip conversion should return the original point"); + SignalTestCompleted(); + return; + } + } + } + + // Now, more extensive testing on a single point. + double screen_x, screen_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &screen_x, &screen_y, NPCoordinateSpaceScreen); + double flipped_screen_x, flipped_screen_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &flipped_screen_x, &flipped_screen_y, + NPCoordinateSpaceFlippedScreen); + double window_x, window_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &window_x, &window_y, NPCoordinateSpaceWindow); + double flipped_window_x, flipped_window_y; + browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin, + &flipped_window_x, &flipped_window_y, + NPCoordinateSpaceFlippedWindow); + + CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID()); + + // Check that all the coordinates are right. The constants below are based on + // the window frame set in the UI test and the content offset in the test + // html. Y-coordinates are not checked exactly so that the test is robust + // against toolbar changes, info and bookmark bar visibility, etc. + const int kWindowHeight = 400; + const int kWindowXOrigin = 50; + const int kWindowYOrigin = 50; + const int kPluginXContentOffset = 50; + const int kPluginYContentOffset = 50; + const int kChromeYTolerance = 200; + + std::string error_string; + if (screen_x != flipped_screen_x) + error_string = "Flipping screen coordinates shouldn't change x"; + else if (flipped_screen_y != main_display_bounds.size.height - screen_y) + error_string = "Flipped screen coordinates should be flipped vertically"; + else if (screen_x != kWindowXOrigin + kPluginXContentOffset) + error_string = "Screen x location is wrong"; + else if (flipped_screen_y < kWindowYOrigin + kPluginYContentOffset || + flipped_screen_y > kWindowYOrigin + kPluginYContentOffset + + kChromeYTolerance) + error_string = "Screen y location is wrong"; + else if (window_x != flipped_window_x) + error_string = "Flipping window coordinates shouldn't change x"; + else if (flipped_window_y != kWindowHeight - window_y) + error_string = "Flipped window coordinates should be flipped vertically"; + else if (window_x != kPluginXContentOffset) + error_string = "Window x location is wrong"; + else if (flipped_window_y < kPluginYContentOffset || + flipped_window_y > kPluginYContentOffset + kChromeYTolerance) + error_string = "Window y location is wrong"; + + if (!error_string.empty()) { + error_string.append(" - "); + error_string.append(StringForPoint(screen_x, screen_y)); + error_string.append(" - "); + error_string.append(StringForPoint(flipped_screen_x, flipped_screen_y)); + error_string.append(" - "); + error_string.append(StringForPoint(window_x, window_y)); + error_string.append(" - "); + error_string.append(StringForPoint(flipped_window_x, flipped_window_y)); + SetError(error_string); + } +#else + SetError("Unimplemented"); +#endif + SignalTestCompleted(); +} + +} // namespace NPAPIClient diff --git a/webkit/glue/plugins/test/plugin_windowless_test.h b/webkit/glue/plugins/test/plugin_windowless_test.h new file mode 100644 index 0000000..f336653 --- /dev/null +++ b/webkit/glue/plugins/test/plugin_windowless_test.h @@ -0,0 +1,35 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H +#define WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H + +#include "webkit/glue/plugins/test/plugin_test.h" + +namespace NPAPIClient { + +// This class contains a list of windowless plugin tests. Please add additional +// tests to this class. +class WindowlessPluginTest : public PluginTest { + public: + // Constructor. + WindowlessPluginTest(NPP id, NPNetscapeFuncs *host_functions); + + // These tests run in windowless plugin mode. + virtual bool IsWindowless() const { return true; } + + // NPAPI HandleEvent handler + virtual int16 HandleEvent(void* event); + + protected: + NPError ExecuteScript(NPNetscapeFuncs* browser, NPP id, + const std::string& script, NPVariant* result); + void ExecuteScriptDeleteInPaint(NPNetscapeFuncs* browser); + void MultipleInstanceSyncCalls(NPNetscapeFuncs* browser); + void ConvertPoint(NPNetscapeFuncs* browser); +}; + +} // namespace NPAPIClient + +#endif // WEBKIT_GLUE_PLUGINS_TEST_PLUGIN_EXECUTE_SCRIPT_DELETE_TEST_H diff --git a/webkit/glue/plugins/test/resource.h b/webkit/glue/plugins/test/resource.h new file mode 100644 index 0000000..c52fa82 --- /dev/null +++ b/webkit/glue/plugins/test/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by npapi_test.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/webkit/glue/plugins/webplugin.cc b/webkit/glue/plugins/webplugin.cc new file mode 100644 index 0000000..6443318 --- /dev/null +++ b/webkit/glue/plugins/webplugin.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/webplugin.h" + +namespace webkit_glue { + +WebPluginGeometry::WebPluginGeometry() + : window(gfx::kNullPluginWindow), + rects_valid(false), + visible(false) { +} + +bool WebPluginGeometry::Equals(const WebPluginGeometry& rhs) const { + return window == rhs.window && + window_rect == rhs.window_rect && + clip_rect == rhs.clip_rect && + cutout_rects == rhs.cutout_rects && + rects_valid == rhs.rects_valid && + visible == rhs.visible; +} +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin.h b/webkit/glue/plugins/webplugin.h new file mode 100644 index 0000000..1813842 --- /dev/null +++ b/webkit/glue/plugins/webplugin.h @@ -0,0 +1,195 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_H_ +#define WEBKIT_GLUE_WEBPLUGIN_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "gfx/native_widget_types.h" +#include "gfx/rect.h" + +// TODO(port): this typedef is obviously incorrect on non-Windows +// platforms, but now a lot of code now accidentally depends on them +// existing. #ifdef out these declarations and fix all the users. +typedef void* HANDLE; + +class GURL; +struct NPObject; + +namespace WebKit { +class WebFrame; +} + +namespace webkit_glue { + +class WebPluginDelegate; +class WebPluginParentView; +class WebPluginResourceClient; + +// Describes the new location for a plugin window. +struct WebPluginGeometry { + WebPluginGeometry(); + + bool Equals(const WebPluginGeometry& rhs) const; + + // On Windows, this is the plugin window in the plugin process. + // On X11, this is the XID of the plugin-side GtkPlug containing the + // GtkSocket hosting the actual plugin window. + // + // On Mac OS X, all of the plugin types are currently "windowless" + // (window == 0) except for the special case of the GPU plugin, + // which currently performs rendering on behalf of the Pepper 3D API + // and WebGL. The GPU plugin uses a simple integer for the + // PluginWindowHandle which is used to map to a side data structure + // containing information about the plugin. Soon this plugin will be + // generalized, at which point this mechanism will be rethought or + // removed. + gfx::PluginWindowHandle window; + gfx::Rect window_rect; + // Clip rect (include) and cutouts (excludes), relative to + // window_rect origin. + gfx::Rect clip_rect; + std::vector<gfx::Rect> cutout_rects; + bool rects_valid; + bool visible; +}; + +// The WebKit side of a plugin implementation. It provides wrappers around +// operations that need to interact with the frame and other WebCore objects. +class WebPlugin { + public: + virtual ~WebPlugin() {} + + // Called by the plugin delegate to let the WebPlugin know if the plugin is + // windowed (i.e. handle is not NULL) or windowless (handle is NULL). This + // tells the WebPlugin to send mouse/keyboard events to the plugin delegate, + // as well as the information about the HDC for paint operations. + virtual void SetWindow(gfx::PluginWindowHandle window) = 0; + + // Whether input events should be sent to the delegate. + virtual void SetAcceptsInputEvents(bool accepts) = 0; + + // Called by the plugin delegate to let it know that the window is being + // destroyed. + virtual void WillDestroyWindow(gfx::PluginWindowHandle window) = 0; +#if defined(OS_WIN) + // The pump_messages_event is a event handle which is valid only for + // windowless plugins and is used in NPP_HandleEvent calls to pump messages + // if the plugin enters a modal loop. + // Cancels a pending request. + virtual void SetWindowlessPumpEvent(HANDLE pump_messages_event) = 0; +#endif + virtual void CancelResource(unsigned long id) = 0; + virtual void Invalidate() = 0; + virtual void InvalidateRect(const gfx::Rect& rect) = 0; + + // Returns the NPObject for the browser's window object. + virtual NPObject* GetWindowScriptNPObject() = 0; + + // Returns the DOM element that loaded the plugin. + virtual NPObject* GetPluginElement() = 0; + + // Cookies + virtual void SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) = 0; + virtual std::string GetCookies(const GURL& url, + const GURL& first_party_for_cookies) = 0; + + // Shows a modal HTML dialog containing the given URL. json_arguments are + // passed to the dialog via the DOM 'window.chrome.dialogArguments', and the + // retval is the string returned by 'window.chrome.send("DialogClose", + // retval)'. + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval) = 0; + + // When a default plugin has downloaded the plugin list and finds it is + // available, it calls this method to notify the renderer. Also it will update + // the status when user clicks on the plugin to install. + virtual void OnMissingPluginStatus(int status) = 0; + + // Handles GetURL/GetURLNotify/PostURL/PostURLNotify requests initiated + // by plugins. If the plugin wants notification of the result, notify_id will + // be non-zero. + virtual void HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed) = 0; + + // Cancels document load. + virtual void CancelDocumentLoad() = 0; + + // Initiates a HTTP range request for an existing stream. + virtual void InitiateHTTPRangeRequest(const char* url, + const char* range_info, + int range_request_id) = 0; + + // Returns true iff in off the record (Incognito) mode. + virtual bool IsOffTheRecord() = 0; + + // Called when the WebPluginResourceClient instance is deleted. + virtual void ResourceClientDeleted( + WebPluginResourceClient* resource_client) {} + + // Defers the loading of the resource identified by resource_id. This is + // controlled by the defer parameter. + virtual void SetDeferResourceLoading(unsigned long resource_id, + bool defer) = 0; + +#if defined(OS_MACOSX) + // Synthesize a fake window handle for the plug-in to identify the instance + // to the browser, allowing mapping to a surface for hardware accelleration + // of plug-in content. The browser generates the handle which is then set on + // the plug-in. |opaque| indicates whether the content should be treated as + // opaque or translucent. + virtual void BindFakePluginWindowHandle(bool opaque) {} + + // Tell the browser (via the renderer) to invalidate because the + // accelerated buffers have changed. + virtual void AcceleratedFrameBuffersDidSwap(gfx::PluginWindowHandle window) {} + + // Tell the renderer and browser to associate the given plugin handle with + // |accelerated_surface_identifier|. The geometry is used to resize any + // native "window" (which on the Mac is a CALayer). + virtual void SetAcceleratedSurface(gfx::PluginWindowHandle window, + int32 width, + int32 height, + uint64 accelerated_surface_identifier) {} +#endif + + // Gets the WebPluginDelegate that implements the interface. + // This API is only for use with Pepper, and is only overridden + // by in-renderer implementations. + virtual WebPluginDelegate* delegate() { return NULL; } +}; + +// Simpler version of ResourceHandleClient that lends itself to proxying. +class WebPluginResourceClient { + public: + virtual ~WebPluginResourceClient() {} + virtual void WillSendRequest(const GURL& url) = 0; + // The request_is_seekable parameter indicates whether byte range requests + // can be issued for the underlying stream. + virtual void DidReceiveResponse(const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified, + bool request_is_seekable) = 0; + virtual void DidReceiveData(const char* buffer, int length, + int data_offset) = 0; + virtual void DidFinishLoading() = 0; + virtual void DidFail() = 0; + virtual bool IsMultiByteResponseExpected() = 0; +}; + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_WEBPLUGIN_H_ diff --git a/webkit/glue/plugins/webplugin_2d_device_delegate.h b/webkit/glue/plugins/webplugin_2d_device_delegate.h new file mode 100644 index 0000000..69bd53a --- /dev/null +++ b/webkit/glue/plugins/webplugin_2d_device_delegate.h @@ -0,0 +1,57 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI 2D device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPlugin2DDeviceDelegate { + public: + virtual NPError Device2DQueryCapability(int32 capability, int32* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DQueryConfig(const NPDeviceContext2DConfig* request, + NPDeviceContext2DConfig* obtain) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DInitializeContext( + const NPDeviceContext2DConfig* config, + NPDeviceContext2D* context) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DSetStateContext(NPDeviceContext2D* context, + int32 state, + intptr_t value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DGetStateContext(NPDeviceContext2D* context, + int32 state, + intptr_t* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DFlushContext(NPP id, + NPDeviceContext2D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device2DDestroyContext(NPDeviceContext2D* context) { + return NPERR_GENERIC_ERROR; + } + + protected: + WebPlugin2DDeviceDelegate() {} + virtual ~WebPlugin2DDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_2D_DEVICE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_3d_device_delegate.h b/webkit/glue/plugins/webplugin_3d_device_delegate.h new file mode 100644 index 0000000..fbb46eb --- /dev/null +++ b/webkit/glue/plugins/webplugin_3d_device_delegate.h @@ -0,0 +1,101 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI 3D device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPlugin3DDeviceDelegate { + public: + virtual NPError Device3DQueryCapability(int32 capability, int32* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DQueryConfig(const NPDeviceContext3DConfig* request, + NPDeviceContext3DConfig* obtain) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DInitializeContext( + const NPDeviceContext3DConfig* config, + NPDeviceContext3D* context) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DSetStateContext(NPDeviceContext3D* context, + int32 state, + intptr_t value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DGetStateContext(NPDeviceContext3D* context, + int32 state, + intptr_t* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DFlushContext(NPP id, + NPDeviceContext3D* context, + NPDeviceFlushContextCallbackPtr callback, + void* user_data) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DDestroyContext(NPDeviceContext3D* context) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DCreateBuffer(NPDeviceContext3D* context, + size_t size, + int32* id) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DDestroyBuffer(NPDeviceContext3D* context, + int32 id) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DMapBuffer(NPDeviceContext3D* context, + int32 id, + NPDeviceBuffer* buffer) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DGetNumConfigs(int32* num_configs) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DGetConfigAttribs(int32 config, + int32* attrib_list) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DCreateContext(int32 config, + const int32* attrib_list, + NPDeviceContext3D** context) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DRegisterCallback( + NPP id, + NPDeviceContext* context, + int32 callback_type, + NPDeviceGenericCallbackPtr callback, + void* callback_data) { + return NPERR_GENERIC_ERROR; + } + virtual NPError Device3DSynchronizeContext( + NPP id, + NPDeviceContext3D* context, + NPDeviceSynchronizationMode mode, + const int32* input_attrib_list, + int32* output_attrib_list, + NPDeviceSynchronizeContextCallbackPtr callback, + void* callback_data) { + return NPERR_GENERIC_ERROR; + } + + protected: + WebPlugin3DDeviceDelegate() {} + virtual ~WebPlugin3DDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_3D_DEVICE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_audio_device_delegate.h b/webkit/glue/plugins/webplugin_audio_device_delegate.h new file mode 100644 index 0000000..3f37246 --- /dev/null +++ b/webkit/glue/plugins/webplugin_audio_device_delegate.h @@ -0,0 +1,56 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI audio device extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginAudioDeviceDelegate { + public: + virtual NPError DeviceAudioQueryCapability(int32 capability, int32* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioQueryConfig( + const NPDeviceContextAudioConfig* request, + NPDeviceContextAudioConfig* obtain) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioInitializeContext( + const NPDeviceContextAudioConfig* config, + NPDeviceContextAudio* context) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioSetStateContext(NPDeviceContextAudio* context, + int32 state, intptr_t value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioGetStateContext(NPDeviceContextAudio* context, + int32 state, intptr_t* value) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioFlushContext( + NPP id, NPDeviceContextAudio* context, + NPDeviceFlushContextCallbackPtr callback, void* user_data) { + return NPERR_GENERIC_ERROR; + } + virtual NPError DeviceAudioDestroyContext(NPDeviceContextAudio* context) { + return NPERR_GENERIC_ERROR; + } + + protected: + WebPluginAudioDeviceDelegate() {} + virtual ~WebPluginAudioDeviceDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_AUDIO_DEVICE_DELEGATE_H_ + diff --git a/webkit/glue/plugins/webplugin_delegate.h b/webkit/glue/plugins/webplugin_delegate.h new file mode 100644 index 0000000..901cdea --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate.h @@ -0,0 +1,166 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ +#define WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "build/build_config.h" +#include "gfx/native_widget_types.h" +#include "third_party/npapi/bindings/npapi.h" +#include "third_party/npapi/bindings/npapi_extensions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" +#include "webkit/glue/plugins/webplugin_2d_device_delegate.h" +#include "webkit/glue/plugins/webplugin_3d_device_delegate.h" +#include "webkit/glue/plugins/webplugin_audio_device_delegate.h" +#include "webkit/glue/plugins/webplugin_file_delegate.h" +#include "webkit/glue/plugins/webplugin_print_delegate.h" + +class FilePath; +class GURL; +struct NPObject; + +namespace WebKit { +class WebInputEvent; +struct WebCursorInfo; +} + +namespace gfx { +class Rect; +} + +namespace webkit_glue { + +class WebPlugin; +class WebPluginResourceClient; + +// This is the interface that a plugin implementation needs to provide. +class WebPluginDelegate : public WebPlugin2DDeviceDelegate, + public WebPlugin3DDeviceDelegate, + public WebPluginAudioDeviceDelegate, + public WebPluginPrintDelegate, + public WebPluginFileDelegate { + public: + virtual ~WebPluginDelegate() {} + + // Initializes the plugin implementation with the given (UTF8) arguments. + // Note that the lifetime of WebPlugin must be longer than this delegate. + // If this function returns false the plugin isn't started and shouldn't be + // called again. If this method succeeds, then the WebPlugin is valid until + // PluginDestroyed is called. + // The load_manually parameter if true indicates that the plugin data would + // be passed from webkit. if false indicates that the plugin should download + // the data. This also controls whether the plugin is instantiated as a full + // page plugin (NP_FULL) or embedded (NP_EMBED). + virtual bool Initialize(const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + WebPlugin* plugin, + bool load_manually) = 0; + + // Called when the WebPlugin is being destroyed. This is a signal to the + // delegate that it should tear-down the plugin implementation and not call + // methods on the WebPlugin again. + virtual void PluginDestroyed() = 0; + + // Update the geometry of the plugin. This is a request to move the + // plugin, relative to its containing window, to the coords given by + // window_rect. Its contents should be clipped to the coords given + // by clip_rect, which are relative to the origin of the plugin + // window. The clip_rect is in plugin-relative coordinates. + virtual void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) = 0; + + // Tells the plugin to paint the damaged rect. |canvas| is only used for + // windowless plugins. + virtual void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& rect) = 0; + + // Tells the plugin to print itself. + virtual void Print(gfx::NativeDrawingContext hdc) = 0; + + // Informs the plugin that it has gained or lost focus. This is only called in + // windowless mode. + virtual void SetFocus(bool focused) = 0; + + // For windowless plugins, gives them a user event like mouse/keyboard. + // Returns whether the event was handled. This is only called in windowsless + // mode. See NPAPI NPP_HandleEvent for more information. + virtual bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor) = 0; + + // Gets the NPObject associated with the plugin for scripting. + virtual NPObject* GetPluginScriptableObject() = 0; + + // Receives notification about a resource load that the plugin initiated + // for a frame. + virtual void DidFinishLoadWithReason(const GURL& url, NPReason reason, + int notify_id) = 0; + + // Returns the process id of the process that is running the plugin. + virtual int GetProcessId() = 0; + + // The result, UTF-8 encoded, of the script execution is returned via this + // function. + virtual void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) = 0; + + // Receives notification about data being available. + virtual void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified) = 0; + + // Receives the data. + virtual void DidReceiveManualData(const char* buffer, int length) = 0; + + // Indicates end of data load. + virtual void DidFinishManualLoading() = 0; + + // Indicates a failure in data receipt. + virtual void DidManualLoadFail() = 0; + + // Only supported when the plugin is the default plugin. + virtual void InstallMissingPlugin() = 0; + + // Creates a WebPluginResourceClient instance and returns the same. + virtual WebPluginResourceClient* CreateResourceClient( + unsigned long resource_id, + const GURL& url, + int notify_id) = 0; + + // Creates a WebPluginResourceClient instance for an existing stream that is + // has become seekable. + virtual WebPluginResourceClient* CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id) = 0; + + // See WebPluginContainerImpl's description of the interface. + virtual bool StartFind(const string16& search_text, + bool case_sensitive, + int identifier) { return false; } + virtual void SelectFindResult(bool forward) {} + virtual void StopFind() {} + virtual void NumberOfFindResultsChanged(int total, bool final_result) {} + virtual void SelectedFindResultChanged(int index) {} + virtual NPWidgetExtensions* GetWidgetExtensions() { return NULL; } + virtual bool SetCursor(NPCursorType type) { return false; } + virtual NPFontExtensions* GetFontExtensions() { return NULL; } + + // Used for zooming of full page plugins. 0 means reset, while -1 means zoom + // out and +1 means zoom in. + virtual void SetZoomFactor(float scale, bool text_only) {} + // Gets the selected text, if any. + virtual bool HasSelection() const { return false; } + virtual string16 GetSelectionAsText() const { return string16(); } + virtual string16 GetSelectionAsMarkup() const { return string16(); } +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBPLUGIN_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_delegate_impl.cc b/webkit/glue/plugins/webplugin_delegate_impl.cc new file mode 100644 index 0000000..b73b5ae --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/webkit_glue.h" + +using webkit_glue::WebPlugin; +using webkit_glue::WebPluginDelegate; +using webkit_glue::WebPluginResourceClient; +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +WebPluginDelegateImpl* WebPluginDelegateImpl::Create( + const FilePath& filename, + const std::string& mime_type, + gfx::PluginWindowHandle containing_view) { + scoped_refptr<NPAPI::PluginLib> plugin_lib = + NPAPI::PluginLib::CreatePluginLib(filename); + if (plugin_lib.get() == NULL) + return NULL; + + NPError err = plugin_lib->NP_Initialize(); + if (err != NPERR_NO_ERROR) + return NULL; + + scoped_refptr<NPAPI::PluginInstance> instance = + plugin_lib->CreateInstance(mime_type); + return new WebPluginDelegateImpl(containing_view, instance.get()); +} + +void WebPluginDelegateImpl::PluginDestroyed() { + if (handle_event_depth_) { + MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } else { + delete this; + } +} + +bool WebPluginDelegateImpl::Initialize( + const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + WebPlugin* plugin, + bool load_manually) { + plugin_ = plugin; + + instance_->set_web_plugin(plugin_); + if (quirks_ & PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES) { + NPAPI::PluginLib* plugin_lib = instance()->plugin_lib(); + if (plugin_lib->instance_count() > 1) { + return false; + } + } + + if (quirks_ & PLUGIN_QUIRK_DIE_AFTER_UNLOAD) + webkit_glue::SetForcefullyTerminatePluginProcess(true); + + int argc = 0; + scoped_array<char*> argn(new char*[arg_names.size()]); + scoped_array<char*> argv(new char*[arg_names.size()]); + for (size_t i = 0; i < arg_names.size(); ++i) { + if (quirks_ & PLUGIN_QUIRK_NO_WINDOWLESS && + LowerCaseEqualsASCII(arg_names[i], "windowlessvideo")) { + continue; + } + argn[argc] = const_cast<char*>(arg_names[i].c_str()); + argv[argc] = const_cast<char*>(arg_values[i].c_str()); + argc++; + } + + bool start_result = instance_->Start( + url, argn.get(), argv.get(), argc, load_manually); + if (!start_result) + return false; + + windowless_ = instance_->windowless(); + if (!windowless_) { + if (!WindowedCreatePlugin()) + return false; + } else { + // For windowless plugins we should set the containing window handle + // as the instance window handle. This is what Safari does. Not having + // a valid window handle causes subtle bugs with plugins which retrieve + // the window handle and validate the same. The window handle can be + // retrieved via NPN_GetValue of NPNVnetscapeWindow. + instance_->set_window_handle(parent_); + } + + bool should_load = PlatformInitialize(); + + plugin_url_ = url.spec(); + + return should_load; +} + +void WebPluginDelegateImpl::DestroyInstance() { + if (instance_ && (instance_->npp()->ndata != NULL)) { + // Shutdown all streams before destroying so that + // no streams are left "in progress". Need to do + // this before calling set_web_plugin(NULL) because the + // instance uses the helper to do the download. + instance_->CloseStreams(); + + window_.window = NULL; + if (!(quirks_ & PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY)) { + instance_->NPP_SetWindow(&window_); + } + + instance_->NPP_Destroy(); + + instance_->set_web_plugin(NULL); + + PlatformDestroyInstance(); + + instance_ = 0; + } +} + +void WebPluginDelegateImpl::UpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + + if (first_set_window_call_) { + first_set_window_call_ = false; + // Plugins like media player on Windows have a bug where in they handle the + // first geometry update and ignore the rest resulting in painting issues. + // This quirk basically ignores the first set window call sequence for + // these plugins and has been tested for Windows plugins only. + if (quirks_ & PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL) + return; + } + + if (windowless_) { + WindowlessUpdateGeometry(window_rect, clip_rect); + } else { + WindowedUpdateGeometry(window_rect, clip_rect); + } +} + +NPObject* WebPluginDelegateImpl::GetPluginScriptableObject() { + return instance_->GetPluginScriptableObject(); +} + +void WebPluginDelegateImpl::DidFinishLoadWithReason(const GURL& url, + NPReason reason, + int notify_id) { + if (quirks_ & PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS && + reason == NPRES_NETWORK_ERR) { + // Flash needs this or otherwise it unloads the launching swf object. + reason = NPRES_DONE; + } + + instance()->DidFinishLoadWithReason(url, reason, notify_id); +} + +int WebPluginDelegateImpl::GetProcessId() { + // We are in process, so the plugin pid is this current process pid. + return base::GetCurrentProcId(); +} + +void WebPluginDelegateImpl::SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id) { + instance()->SendJavaScriptStream(url, result, success, notify_id); +} + +void WebPluginDelegateImpl::DidReceiveManualResponse( + const GURL& url, const std::string& mime_type, + const std::string& headers, uint32 expected_length, uint32 last_modified) { + if (!windowless_) { + // Calling NPP_WriteReady before NPP_SetWindow causes movies to not load in + // Flash. See http://b/issue?id=892174. + DCHECK(windowed_did_set_window_); + } + + instance()->DidReceiveManualResponse(url, mime_type, headers, + expected_length, last_modified); +} + +void WebPluginDelegateImpl::DidReceiveManualData(const char* buffer, + int length) { + instance()->DidReceiveManualData(buffer, length); +} + +void WebPluginDelegateImpl::DidFinishManualLoading() { + instance()->DidFinishManualLoading(); +} + +void WebPluginDelegateImpl::DidManualLoadFail() { + instance()->DidManualLoadFail(); +} + +FilePath WebPluginDelegateImpl::GetPluginPath() { + return instance()->plugin_lib()->plugin_info().path; +} + +void WebPluginDelegateImpl::WindowedUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (WindowedReposition(window_rect, clip_rect) || + !windowed_did_set_window_) { + // Let the plugin know that it has been moved + WindowedSetWindow(); + } +} + +bool WebPluginDelegateImpl::HandleInputEvent(const WebInputEvent& event, + WebCursorInfo* cursor_info) { + DCHECK(windowless_) << "events should only be received in windowless mode"; + + bool pop_user_gesture = false; + if (IsUserGesture(event)) { + pop_user_gesture = true; + instance()->PushPopupsEnabledState(true); + } + + bool handled = PlatformHandleInputEvent(event, cursor_info); + + if (pop_user_gesture) { + instance()->PopPopupsEnabledState(); + } + + return handled; +} + +bool WebPluginDelegateImpl::IsUserGesture(const WebInputEvent& event) { + switch (event.type) { + case WebInputEvent::MouseDown: + case WebInputEvent::MouseUp: + case WebInputEvent::KeyDown: + case WebInputEvent::KeyUp: + return true; + default: + return false; + } + return false; +} + +WebPluginResourceClient* WebPluginDelegateImpl::CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id) { + return instance()->CreateStream( + resource_id, url, std::string(), notify_id); +} + +WebPluginResourceClient* WebPluginDelegateImpl::CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id) { + return instance()->GetRangeRequest(range_request_id); +} diff --git a/webkit/glue/plugins/webplugin_delegate_impl.h b/webkit/glue/plugins/webplugin_delegate_impl.h new file mode 100644 index 0000000..ebf5d3e --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl.h @@ -0,0 +1,490 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGIN_WEBPLUGIN_DELEGATE_IMPL_H_ +#define WEBKIT_GLUE_PLUGIN_WEBPLUGIN_DELEGATE_IMPL_H_ + +#include "build/build_config.h" + +#include <string> +#include <list> +#include <set> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "base/time.h" +#include "base/timer.h" +#include "gfx/native_widget_types.h" +#include "gfx/rect.h" +#include "third_party/npapi/bindings/npapi.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/webcursor.h" + +#if defined(OS_MACOSX) +#include "app/surface/accelerated_surface_mac.h" +#endif + +#if defined(USE_X11) +typedef struct _GdkDrawable GdkPixmap; +#endif + +namespace NPAPI { +class PluginInstance; +} + +namespace WebKit { +class WebMouseEvent; +} + +#if defined(OS_MACOSX) +class ExternalDragTracker; +#ifndef NP_NO_QUICKDRAW +class QuickDrawDrawingManager; +#endif +#ifdef __OBJC__ +@class CALayer; +@class CARenderer; +#else +class CALayer; +class CARenderer; +#endif +#endif + +// An implementation of WebPluginDelegate that runs in the plugin process, +// proxied from the renderer by WebPluginDelegateProxy. +class WebPluginDelegateImpl : public webkit_glue::WebPluginDelegate { + public: + enum PluginQuirks { + PLUGIN_QUIRK_SETWINDOW_TWICE = 1, // Win32 + PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE = 2, // Win32 + PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY = 4, // Win32 + PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY = 8, // Win32 + PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES = 16, // Win32 + PLUGIN_QUIRK_DIE_AFTER_UNLOAD = 32, // Win32 + PLUGIN_QUIRK_PATCH_SETCURSOR = 64, // Win32 + PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS = 128, // Win32 + PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW = 256, // Linux + PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW = 512, // Linux + PLUGIN_QUIRK_NO_WINDOWLESS = 1024, // Windows + PLUGIN_QUIRK_PATCH_REGENUMKEYEXW = 2048, // Windows + PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS = 4096, // Windows + PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH = 8192, // Mac + PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE = 16384, // Windows + PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK = 32768, // Linux + PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL = 65536, // Windows. + }; + + static WebPluginDelegateImpl* Create(const FilePath& filename, + const std::string& mime_type, + gfx::PluginWindowHandle containing_view); + + static bool IsPluginDelegateWindow(gfx::NativeWindow window); + static bool GetPluginNameFromWindow(gfx::NativeWindow window, + std::wstring *plugin_name); + + // Returns true if the window handle passed in is that of the dummy + // activation window for windowless plugins. + static bool IsDummyActivationWindow(gfx::NativeWindow window); + + // WebPluginDelegate implementation + virtual bool Initialize(const GURL& url, + const std::vector<std::string>& arg_names, + const std::vector<std::string>& arg_values, + webkit_glue::WebPlugin* plugin, + bool load_manually); + virtual void PluginDestroyed(); + virtual void UpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + virtual void Paint(WebKit::WebCanvas* canvas, const gfx::Rect& rect); + virtual void Print(gfx::NativeDrawingContext context); + virtual void SetFocus(bool focused); + virtual bool HandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor_info); + virtual NPObject* GetPluginScriptableObject(); + virtual void DidFinishLoadWithReason( + const GURL& url, NPReason reason, int notify_id); + virtual int GetProcessId(); + virtual void SendJavaScriptStream(const GURL& url, + const std::string& result, + bool success, + int notify_id); + virtual void DidReceiveManualResponse(const GURL& url, + const std::string& mime_type, + const std::string& headers, + uint32 expected_length, + uint32 last_modified); + virtual void DidReceiveManualData(const char* buffer, int length); + virtual void DidFinishManualLoading(); + virtual void DidManualLoadFail(); + virtual void InstallMissingPlugin(); + virtual webkit_glue::WebPluginResourceClient* CreateResourceClient( + unsigned long resource_id, const GURL& url, int notify_id); + virtual webkit_glue::WebPluginResourceClient* CreateSeekableResourceClient( + unsigned long resource_id, int range_request_id); + // End of WebPluginDelegate implementation. + + bool IsWindowless() const { return windowless_ ; } + gfx::Rect GetRect() const { return window_rect_; } + gfx::Rect GetClipRect() const { return clip_rect_; } + + // Returns the path for the library implementing this plugin. + FilePath GetPluginPath(); + + // Returns a combination of PluginQuirks. + int GetQuirks() const { return quirks_; } + +#if defined(OS_MACOSX) + // Informs the plugin that the geometry has changed, as with UpdateGeometry, + // but also includes the new buffer context for that new geometry. + void UpdateGeometryAndContext(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect, + gfx::NativeDrawingContext context); + // Informs the delegate that the plugin called NPN_Invalidate*. Used as a + // trigger for Core Animation drawing. + void PluginDidInvalidate(); + // Returns the delegate currently processing events. + static WebPluginDelegateImpl* GetActiveDelegate(); + // Informs the plugin that the window it is in has gained or lost focus. + void SetWindowHasFocus(bool has_focus); + // Informs the plugin that the view it is in has gained or lost first + // responder status. + void SetContentAreaHasFocus(bool has_focus); + // Returns whether or not the window the plugin is in has focus. + bool GetWindowHasFocus() const { return containing_window_has_focus_; } + // Informs the plugin that its tab or window has been hidden or shown. + void SetContainerVisibility(bool is_visible); + // Informs the plugin that its containing window's frame has changed. + // Frames are in screen coordinates. + void WindowFrameChanged(gfx::Rect window_frame, gfx::Rect view_frame); + // Informs the delegate that the plugin set a Carbon ThemeCursor. + void SetThemeCursor(ThemeCursor cursor); + // Informs the delegate that the plugin set a Carbon Cursor. + void SetCursor(const Cursor* cursor); + // Informs the delegate that the plugin set a Cocoa NSCursor. + void SetNSCursor(NSCursor* cursor); + +#ifndef NP_NO_CARBON + // Indicates that it's time to send the plugin a null event. + void FireIdleEvent(); +#endif +#endif // OS_MACOSX + + gfx::PluginWindowHandle windowed_handle() const { + return windowed_handle_; + } + +#if defined(OS_MACOSX) + // Allow setting a "fake" window handle to associate this plug-in with + // an IOSurface in the browser. Used for accelerated drawing surfaces. + void set_windowed_handle(gfx::PluginWindowHandle handle); +#endif + + private: + friend class DeleteTask<WebPluginDelegateImpl>; + friend class webkit_glue::WebPluginDelegate; + + WebPluginDelegateImpl(gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance); + ~WebPluginDelegateImpl(); + + // Called by Initialize() for platform-specific initialization. + // If this returns false, the plugin shouldn't be started--see Initialize(). + bool PlatformInitialize(); + + // Called by DestroyInstance(), used for platform-specific destruction. + void PlatformDestroyInstance(); + + //-------------------------- + // used for windowed plugins + void WindowedUpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + // Create the native window. + // Returns true if the window is created (or already exists). + // Returns false if unable to create the window. + bool WindowedCreatePlugin(); + + // Destroy the native window. + void WindowedDestroyWindow(); + + // Reposition the native window to be in sync with the given geometry. + // Returns true if the native window has moved or been clipped differently. + bool WindowedReposition(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + + // Tells the plugin about the current state of the window. + // See NPAPI NPP_SetWindow for more information. + void WindowedSetWindow(); + +#if defined(OS_WIN) + // Registers the window class for our window + ATOM RegisterNativeWindowClass(); + + // Our WndProc functions. + static LRESULT CALLBACK DummyWindowProc( + HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK NativeWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + static LRESULT CALLBACK FlashWindowlessWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + // Used for throttling Flash messages. + static void ClearThrottleQueueForWindow(HWND window); + static void OnThrottleMessage(); + static void ThrottleMessage(WNDPROC proc, HWND hwnd, UINT message, + WPARAM wParam, LPARAM lParam); +#endif + + //---------------------------- + // used for windowless plugins + void WindowlessUpdateGeometry(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect); + void WindowlessPaint(gfx::NativeDrawingContext hdc, const gfx::Rect& rect); + + // Tells the plugin about the current state of the window. + // See NPAPI NPP_SetWindow for more information. + void WindowlessSetWindow(); + + //----------------------------------------- + // used for windowed and windowless plugins + + // Does platform-specific event handling. Arguments and return are identical + // to HandleInputEvent. + bool PlatformHandleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo* cursor_info); + + NPAPI::PluginInstance* instance() { return instance_.get(); } + + // Closes down and destroys our plugin instance. + void DestroyInstance(); + + + // used for windowed plugins + // Note: on Mac OS X, the only time the windowed handle is non-zero + // is the case of accelerated rendering, which uses a fake window handle to + // identify itself back to the browser. It still performs all of its + // work offscreen. + gfx::PluginWindowHandle windowed_handle_; + gfx::Rect windowed_last_pos_; + + bool windowed_did_set_window_; + + // used by windowed and windowless plugins + bool windowless_; + + webkit_glue::WebPlugin* plugin_; + scoped_refptr<NPAPI::PluginInstance> instance_; + +#if defined(OS_WIN) + // Original wndproc before we subclassed. + WNDPROC plugin_wnd_proc_; + + // Used to throttle WM_USER+1 messages in Flash. + uint32 last_message_; + bool is_calling_wndproc; + + // The current keyboard layout of this process and the main thread ID of the + // browser process. These variables are used for synchronizing the keyboard + // layout of this process with the one of the browser process. + HKL keyboard_layout_; + int parent_thread_id_; +#endif // OS_WIN + +#if defined(USE_X11) + // The pixmap we're drawing into, for a windowless plugin. + GdkPixmap* pixmap_; + double first_event_time_; + + // On Linux some plugins assume that the GtkSocket container is in the same + // process. So we create a GtkPlug to plug into the browser's container, and + // a GtkSocket to hold the plugin. We then send the GtkPlug to the browser + // process. + GtkWidget* plug_; + GtkWidget* socket_; + + // Ensure pixmap_ exists and is at least width by height pixels. + void EnsurePixmapAtLeastSize(int width, int height); +#endif + + gfx::PluginWindowHandle parent_; + NPWindow window_; + gfx::Rect window_rect_; + gfx::Rect clip_rect_; + int quirks_; + +#if defined(OS_WIN) + // Windowless plugins don't have keyboard focus causing issues with the + // plugin not receiving keyboard events if the plugin enters a modal + // loop like TrackPopupMenuEx or MessageBox, etc. + // This is a basic issue with windows activation and focus arising due to + // the fact that these windows are created by different threads. Activation + // and focus are thread specific states, and if the browser has focus, + // the plugin may not have focus. + // To fix a majority of these activation issues we create a dummy visible + // child window to which we set focus whenever the windowless plugin + // receives a WM_LBUTTONDOWN/WM_RBUTTONDOWN message via NPP_HandleEvent. + + HWND dummy_window_for_activation_; + bool CreateDummyWindowForActivation(); + + // Returns true if the event passed in needs to be tracked for a potential + // modal loop. + static bool ShouldTrackEventForModalLoops(NPEvent* event); + + // The message filter hook procedure, which tracks modal loops entered by + // a plugin in the course of a NPP_HandleEvent call. + static LRESULT CALLBACK HandleEventMessageFilterHook(int code, WPARAM wParam, + LPARAM lParam); + + // TrackPopupMenu interceptor. Parameters are the same as the Win32 function + // TrackPopupMenu. + static BOOL WINAPI TrackPopupMenuPatch(HMENU menu, unsigned int flags, int x, + int y, int reserved, HWND window, + const RECT* rect); + + // SetCursor interceptor for windowless plugins. + static HCURSOR WINAPI SetCursorPatch(HCURSOR cursor); + + // RegEnumKeyExW interceptor. + static LONG WINAPI RegEnumKeyExWPatch( + HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved, + LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time); + + // The mouse hook proc which handles mouse capture in windowed plugins. + static LRESULT CALLBACK MouseHookProc(int code, WPARAM wParam, + LPARAM lParam); + + // Calls SetCapture/ReleaseCapture based on the message type. + static void HandleCaptureForMessage(HWND window, UINT message); + +#elif defined(OS_MACOSX) + // Sets window_rect_ to |rect| + void SetPluginRect(const gfx::Rect& rect); + // Sets content_area_origin to |origin| + void SetContentAreaOrigin(const gfx::Point& origin); + // Updates everything that depends on the plugin's absolute screen location. + void PluginScreenLocationChanged(); + + // Informs the plugin that it has gained or lost keyboard focus (i.e., window + // first responder status). + void SetPluginHasFocus(bool has_focus); + + // Returns the apparent zoom ratio for the given event, as inferred from our + // current knowledge about about where on screen the plugin is. + // This is a temporary workaround for <http://crbug.com/9996>; once that is + // fixed we should have correct event coordinates (or an explicit + // notification of zoom level). + float ApparentEventZoomLevel(const WebKit::WebMouseEvent& event); + + // Informs the browser about the updated accelerated drawing surface. + void UpdateAcceleratedSurface(); + + // Updates anything that depends on plugin visibility. + void PluginVisibilityChanged(); + + // Uses a CARenderer to draw the plug-in's layer in our OpenGL surface. + void DrawLayerInSurface(); + +#ifndef NP_NO_CARBON + // Moves our dummy window to match the current screen location of the plugin. + void UpdateDummyWindowBounds(const gfx::Point& plugin_origin); + +#ifndef NP_NO_QUICKDRAW + // Sets the mode used for QuickDraw plugin drawing. If enabled is true the + // plugin draws into a GWorld that's not connected to a window (the faster + // path), otherwise the plugin draws into our invisible dummy window (which is + // slower, since the call we use to scrape the window contents is much more + // expensive than copying between GWorlds). + void SetQuickDrawFastPathEnabled(bool enabled); +#endif + + // Adjusts the idle event rate for a Carbon plugin based on its current + // visibility. + void UpdateIdleEventRate(); +#endif // !NP_NO_CARBON + + CGContextRef buffer_context_; // Weak ref. + +#ifndef NP_NO_CARBON + NP_CGContext np_cg_context_; +#endif +#ifndef NP_NO_QUICKDRAW + NP_Port qd_port_; + scoped_ptr<QuickDrawDrawingManager> qd_manager_; + base::TimeTicks fast_path_enable_tick_; +#endif + + CALayer* layer_; // Used for CA drawing mode. Weak, retained by plug-in. + AcceleratedSurface* surface_; + CARenderer* renderer_; // Renders layer_ to surface_. + scoped_ptr<base::RepeatingTimer<WebPluginDelegateImpl> > redraw_timer_; + + // The upper-left corner of the web content area in screen coordinates, + // relative to an upper-left (0,0). + gfx::Point content_area_origin_; + + // True if the plugin thinks it has keyboard focus + bool plugin_has_focus_; + // True if the plugin element has focus within the page, regardless of whether + // its containing view is currently the first responder for the window. + bool has_webkit_focus_; + // True if the containing view is the window's first responder. + bool containing_view_has_focus_; + + bool containing_window_has_focus_; + bool initial_window_focus_; + bool container_is_visible_; + bool have_called_set_window_; + + gfx::Rect cached_clip_rect_; + + scoped_ptr<ExternalDragTracker> external_drag_tracker_; +#endif // OS_MACOSX + + // Called by the message filter hook when the plugin enters a modal loop. + void OnModalLoopEntered(); + + // Returns true if the message passed in corresponds to a user gesture. + static bool IsUserGesture(const WebKit::WebInputEvent& event); + + // The url with which the plugin was instantiated. + std::string plugin_url_; + +#if defined(OS_WIN) + // Indicates the end of a user gesture period. + void OnUserGestureEnd(); + + // Handle to the message filter hook + HHOOK handle_event_message_filter_hook_; + + // Event which is set when the plugin enters a modal loop in the course + // of a NPP_HandleEvent call. + HANDLE handle_event_pump_messages_event_; + + // This flag indicates whether we started tracking a user gesture message. + bool user_gesture_message_posted_; + + // Runnable Method Factory used to invoke the OnUserGestureEnd method + // asynchronously. + ScopedRunnableMethodFactory<WebPluginDelegateImpl> user_gesture_msg_factory_; + + // Handle to the mouse hook installed for certain windowed plugins like + // flash. + HHOOK mouse_hook_; +#endif + + // Holds the depth of the HandleEvent callstack. + int handle_event_depth_; + + // Holds the current cursor set by the windowless plugin. + WebCursor current_windowless_cursor_; + + // Set to true initially and indicates if this is the first npp_setwindow + // call received by the plugin. + bool first_set_window_call_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginDelegateImpl); +}; + +#endif // WEBKIT_GLUE_PLUGIN_WEBPLUGIN_DELEGATE_IMPL_H_ diff --git a/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc b/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc new file mode 100644 index 0000000..18b1504 --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_gtk.cc @@ -0,0 +1,709 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <vector> + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "gfx/blit.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/gtk_plugin_container.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +#include "third_party/npapi/bindings/npapi_x11.h" + +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : windowed_handle_(0), + windowed_did_set_window_(false), + windowless_(false), + plugin_(NULL), + instance_(instance), + pixmap_(NULL), + first_event_time_(-1.0), + plug_(NULL), + socket_(NULL), + parent_(containing_view), + quirks_(0), + handle_event_depth_(0), + first_set_window_call_(true) { + memset(&window_, 0, sizeof(window_)); + if (instance_->mime_type() == "application/x-shockwave-flash") { + // Flash is tied to Firefox's whacky behavior with windowless plugins. See + // comments in WindowlessPaint. + // TODO(viettrungluu): PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK: Don't allow + // right-clicks in windowless content since Flash 10.1 (initial release, at + // least) hangs in that case. Remove this once Flash is fixed. + quirks_ |= PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW + | PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW + | PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK; + } + + // TODO(evanm): I played with this for quite a while but couldn't + // figure out a way to make Flash not crash unless I didn't call + // NPP_SetWindow. + // However, after piman's grand refactor of windowed plugins, maybe + // this is no longer necessary. + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + DestroyInstance(); + + if (!windowless_) + WindowedDestroyWindow(); + + if (window_.ws_info) { + // We only ever use ws_info as an NPSetWindowCallbackStruct. + delete static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + } + + if (pixmap_) { + g_object_unref(pixmap_); + pixmap_ = NULL; + } +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + gfx::PluginWindowHandle handle = + windowless_ ? 0 : gtk_plug_get_id(GTK_PLUG(plug_)); + plugin_->SetWindow(handle); + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { + // Nothing to do here. +} + +void WebPluginDelegateImpl::Paint(WebKit::WebCanvas* canvas, + const gfx::Rect& rect) { + if (!windowless_) + return; + cairo_t* context = canvas->beginPlatformPaint(); + WindowlessPaint(context, rect); + canvas->endPlatformPaint(); +} + +void WebPluginDelegateImpl::Print(cairo_t* context) { + NOTIMPLEMENTED(); +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NOTIMPLEMENTED(); +} + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + DCHECK(!windowed_handle_); + DCHECK(!plug_); + + // NPP_GetValue() might write 4 bytes of data to this variable. Don't use a + // single byte bool, use an int instead and make sure it is initialized. + int xembed = 0; + NPError err = instance_->NPP_GetValue(NPPVpluginNeedsXEmbed, &xembed); + if (err != NPERR_NO_ERROR || !xembed) { + NOTIMPLEMENTED() << " windowed plugin but without xembed. " + "See http://code.google.com/p/chromium/issues/detail?id=38229"; + return false; + } + + // Passing 0 as the socket XID creates a plug without plugging it in a socket + // yet, so that it can be latter added with gtk_socket_add_id(). + plug_ = gtk_plug_new(0); + gtk_widget_show(plug_); + socket_ = gtk_socket_new(); + gtk_widget_show(socket_); + gtk_container_add(GTK_CONTAINER(plug_), socket_); + gtk_widget_show_all(plug_); + + // Prevent the plug from being destroyed if the browser kills the container + // window. + g_signal_connect(plug_, "delete-event", G_CALLBACK(gtk_true), NULL); + // Prevent the socket from being destroyed when the plugin removes itself. + g_signal_connect(socket_, "plug_removed", G_CALLBACK(gtk_true), NULL); + + windowed_handle_ = gtk_socket_get_id(GTK_SOCKET(socket_)); + + window_.window = reinterpret_cast<void*>(windowed_handle_); + + if (!window_.ws_info) + window_.ws_info = new NPSetWindowCallbackStruct; + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + extra->display = GDK_DISPLAY(); + extra->visual = DefaultVisual(GDK_DISPLAY(), 0); + extra->depth = DefaultDepth(GDK_DISPLAY(), 0); + extra->colormap = DefaultColormap(GDK_DISPLAY(), 0); + + return true; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + if (plug_) { + plugin_->WillDestroyWindow(gtk_plug_get_id(GTK_PLUG(plug_))); + + gtk_widget_destroy(plug_); + plug_ = NULL; + socket_ = NULL; + windowed_handle_ = 0; + } +} + +bool WebPluginDelegateImpl::WindowedReposition( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (window_rect == window_rect_ && clip_rect == clip_rect_) + return false; + + window_rect_ = window_rect; + clip_rect_ = clip_rect; + + return true; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + if (!instance_) + return; + + if (!windowed_handle_) { + NOTREACHED(); + return; + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=108347 + // If we call NPP_SetWindow with a <= 0 width or height, problems arise in + // Flash (and possibly other plugins). + // TODO(piman): the Mozilla code suggests that for the Java plugin, we should + // still call NPP_SetWindow in that case. We need to verify that. + if (window_rect_.width() <= 0 || window_rect_.height() <= 0) { + return; + } + + instance()->set_window_handle(windowed_handle_); + + DCHECK(!instance()->windowless()); + + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + + //window_.window = windowed_handle_; + window_.type = NPWindowTypeWindow; + + // Reset this flag before entering the instance in case of side-effects. + windowed_did_set_window_ = true; + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); +} + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + // Only resend to the instance if the geometry has changed. + if (window_rect == window_rect_ && clip_rect == clip_rect_) + return; + + clip_rect_ = clip_rect; + window_rect_ = window_rect; + WindowlessSetWindow(); +} + +void WebPluginDelegateImpl::EnsurePixmapAtLeastSize(int width, int height) { + if (pixmap_) { + gint cur_width, cur_height; + gdk_drawable_get_size(pixmap_, &cur_width, &cur_height); + if (cur_width >= width && cur_height >= height) + return; // We are already the appropriate size. + + // Otherwise, we need to recreate ourselves. + g_object_unref(pixmap_); + pixmap_ = NULL; + } + + // |sys_visual| is owned by gdk; we shouldn't free it. + GdkVisual* sys_visual = gdk_visual_get_system(); + pixmap_ = gdk_pixmap_new(NULL, // use width/height/depth params + std::max(1, width), std::max(1, height), + sys_visual->depth); + GdkColormap* colormap = gdk_colormap_new(gdk_visual_get_system(), + FALSE); + gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap_), colormap); + // The GdkDrawable now owns the GdkColormap. + g_object_unref(colormap); +} + +#ifdef DEBUG_RECTANGLES +namespace { + +// Draw a rectangle on a Cairo context. +// Useful for debugging various rectangles involved in drawing plugins. +void DrawDebugRectangle(cairo_t* cairo, + const gfx::Rect& rect, + float r, float g, float b) { + cairo_set_source_rgba(cairo, r, g, b, 0.5); + cairo_rectangle(cairo, rect.x(), rect.y(), + rect.width(), rect.height()); + cairo_stroke(cairo); +} + +} // namespace +#endif + +void WebPluginDelegateImpl::WindowlessPaint(cairo_t* context, + const gfx::Rect& damage_rect) { + // Compare to: + // http://mxr.mozilla.org/firefox/source/layout/generic/nsObjectFrame.cpp: + // nsPluginInstanceOwner::Renderer::NativeDraw(). + + DCHECK(context); + + // TODO(darin): we should avoid calling NPP_SetWindow here since it may + // cause page layout to be invalidated. + + // The actual dirty region is just the intersection of the plugin window and + // the clip window with the damage region. However, the plugin wants to draw + // relative to the containing window's origin, so our pixmap must be from the + // window's origin down to the bottom-right edge of the dirty region. + // + // Typical case: + // X-----------------------------------+-----------------------------+ + // | | | + // | pixmap +-------------------+ | + // | | damage | window | + // | | | | + // | +---+-------------------+-------------+ | + // | | | | clip | | + // | +---+---+-------------------+----------+ | | + // | | | | | | | | + // | | | | draw | | | | + // | | | | | | | | + // +-------+---+---+-------------------+----------+--+ | + // | | | | | | + // | | +-------------------+ | | + // | | | | + // | | plugin | | + // | +--------------------------------------+ | + // | | + // | | + // +-----------------------------------------------------------------+ + // X = origin + // + // NPAPI doesn't properly define which coordinates each of + // - window.clipRect, window.x and window.y in the SetWindow call + // - x and y in GraphicsExpose HandleEvent call + // are relative to, nor does it define what the pixmap is relative to. + // + // Any sane values for them just don't work with the flash plugin. Firefox + // has some interesting behavior. Experiments showed that: + // - window.clipRect is always in the same space as window.x and window.y + // - in the first SetWindow call, or when scrolling, window.x and window.y are + // the coordinates of the plugin relative to the window. + // - whenever only a part of the plugin is drawn, Firefox issues a SetWindow + // call before each GraphicsExpose event, that sets the drawing origin to + // (0, 0) as if the plugin was scrolled to be partially out of the view. The + // GraphicsExpose event has coordinates relative to the "window" (assuming + // that virtual scroll). The pixmap is also relative to the window. It always + // sets the clip rect to the draw rect. + // + // Attempts to deviate from that makes Flash render at the wrong place in the + // pixmap, or render the wrong pixels. + // + // Flash plugin: + // X-----------------------------------------------------------------+ + // | | + // | +-------------------+ "real" window | + // | | damage | | + // | | | | + // | +---+-------------------+-------------+ | + // | | | | "real" clip | | + // | +---+---O===================#==========#==#===============# + // | | | H draw | | | H + // | | | H = pixmap | | | H + // | | | H = "apparent" clip | | | H + // | + +---#-------------------+----------+--+ H + // | | H | | H + // | | H-------------------+ | H + // | | H | H + // | | H plugin | H + // | +-------#------------------------------+ H + // | H H + // | H "apparent" window H + // +---------------#=================================================# + // X = "real" origin + // O = "apparent" origin + // "real" means as seen by Chrome + // "apparent" means as seen by the plugin. + + gfx::Rect draw_rect = window_rect_.Intersect(damage_rect); + + // clip_rect_ is relative to the plugin + gfx::Rect clip_rect_window = clip_rect_; + clip_rect_window.Offset(window_rect_.x(), window_rect_.y()); + draw_rect = draw_rect.Intersect(clip_rect_window); + + // These offsets represent by how much the view is shifted to accomodate + // Flash (the coordinates of X relative to O in the diagram above). + int offset_x = 0; + int offset_y = 0; + if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_OFFSET_WINDOW_TO_DRAW) { + offset_x = -draw_rect.x(); + offset_y = -draw_rect.y(); + window_.clipRect.top = 0; + window_.clipRect.left = 0; + window_.clipRect.bottom = draw_rect.height(); + window_.clipRect.right = draw_rect.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x() - draw_rect.x(); + window_.y = window_rect_.y() - draw_rect.y(); + window_.type = NPWindowTypeDrawable; + DCHECK(window_.ws_info); + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK_EQ(err, NPERR_NO_ERROR); + } + + gfx::Rect pixmap_draw_rect = draw_rect; + pixmap_draw_rect.Offset(offset_x, offset_y); + + gfx::Rect pixmap_rect(0, 0, + pixmap_draw_rect.right(), + pixmap_draw_rect.bottom()); + + EnsurePixmapAtLeastSize(pixmap_rect.width(), pixmap_rect.height()); + + // Copy the current image into the pixmap, so the plugin can draw over + // this background. + cairo_t* cairo = gdk_cairo_create(pixmap_); + BlitContextToContext(cairo, pixmap_draw_rect, context, draw_rect.origin()); + cairo_destroy(cairo); + + // Construct the paint message, targeting the pixmap. + NPEvent np_event = {0}; + XGraphicsExposeEvent &event = np_event.xgraphicsexpose; + event.type = GraphicsExpose; + event.display = GDK_DISPLAY(); + event.drawable = GDK_PIXMAP_XID(pixmap_); + event.x = pixmap_draw_rect.x(); + event.y = pixmap_draw_rect.y(); + event.width = pixmap_draw_rect.width(); + event.height = pixmap_draw_rect.height(); + + // Tell the plugin to paint into the pixmap. + static StatsRate plugin_paint("Plugin.Paint"); + StatsScope<StatsRate> scope(plugin_paint); + NPError err = instance()->NPP_HandleEvent(&np_event); + DCHECK_EQ(err, NPERR_NO_ERROR); + + cairo_save(context); + // Now copy the rendered image pixmap back into the drawing buffer. + gdk_cairo_set_source_pixmap(context, pixmap_, -offset_x, -offset_y); + cairo_rectangle(context, draw_rect.x(), draw_rect.y(), + draw_rect.width(), draw_rect.height()); + cairo_clip(context); + cairo_paint(context); + +#ifdef DEBUG_RECTANGLES + // Draw some debugging rectangles. + // Pixmap rect = blue. + DrawDebugRectangle(context, pixmap_rect, 0, 0, 1); + // Drawing rect = red. + DrawDebugRectangle(context, draw_rect, 1, 0, 0); +#endif + cairo_restore(context); +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + if (window_rect_.IsEmpty()) // wait for geometry to be set. + return; + + DCHECK(instance()->windowless()); + // Mozilla docs say that this window param is not used for windowless + // plugins; rather, the window is passed during the GraphicsExpose event. + DCHECK(window_.window == 0); + + window_.clipRect.top = clip_rect_.y() + window_rect_.y(); + window_.clipRect.left = clip_rect_.x() + window_rect_.x(); + window_.clipRect.bottom = + clip_rect_.y() + clip_rect_.height() + window_rect_.y(); + window_.clipRect.right = + clip_rect_.x() + clip_rect_.width() + window_rect_.x(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + window_.type = NPWindowTypeDrawable; + + if (!window_.ws_info) + window_.ws_info = new NPSetWindowCallbackStruct; + NPSetWindowCallbackStruct* extra = + static_cast<NPSetWindowCallbackStruct*>(window_.ws_info); + extra->display = GDK_DISPLAY(); + extra->visual = DefaultVisual(GDK_DISPLAY(), 0); + extra->depth = DefaultDepth(GDK_DISPLAY(), 0); + extra->colormap = DefaultColormap(GDK_DISPLAY(), 0); + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); + if (quirks_ & PLUGIN_QUIRK_WINDOWLESS_INVALIDATE_AFTER_SET_WINDOW) { + // After a NPP_SetWindow, Flash cancels its timer that generates the + // invalidates until it gets a paint event, but doesn't explicitly call + // NPP_InvalidateRect. + plugin_->InvalidateRect(clip_rect_); + } +} + +void WebPluginDelegateImpl::SetFocus(bool focused) { + DCHECK(instance()->windowless()); + + NPEvent np_event = {0}; + XFocusChangeEvent &event = np_event.xfocus; + event.type = focused ? FocusIn : FocusOut; + event.display = GDK_DISPLAY(); + // Same values as Firefox. .serial and .window stay 0. + event.mode = -1; + event.detail = NotifyDetailNone; + instance()->NPP_HandleEvent(&np_event); +} + +// Converts a WebInputEvent::Modifiers bitfield into a +// corresponding X modifier state. +static int GetXModifierState(int modifiers) { + int x_state = 0; + if (modifiers & WebInputEvent::ControlKey) + x_state |= ControlMask; + if (modifiers & WebInputEvent::ShiftKey) + x_state |= ShiftMask; + if (modifiers & WebInputEvent::AltKey) + x_state |= Mod1Mask; + if (modifiers & WebInputEvent::MetaKey) + x_state |= Mod2Mask; + if (modifiers & WebInputEvent::LeftButtonDown) + x_state |= Button1Mask; + if (modifiers & WebInputEvent::MiddleButtonDown) + x_state |= Button2Mask; + if (modifiers & WebInputEvent::RightButtonDown) + x_state |= Button3Mask; + // TODO(piman@google.com): There are other modifiers, e.g. Num Lock, that + // should be set (and Firefox does), but we didn't keep the information in + // the WebKit event. + return x_state; +} + +static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, + Time timestamp, + NPEvent *np_event) { + np_event->xany.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + + int modifier_state = GetXModifierState(event.modifiers); + + Window root = GDK_ROOT_WINDOW(); + switch (event.type) { + case WebInputEvent::MouseMove: { + np_event->type = MotionNotify; + XMotionEvent &motion_event = np_event->xmotion; + motion_event.root = root; + motion_event.time = timestamp; + motion_event.x = event.x; + motion_event.y = event.y; + motion_event.x_root = event.globalX; + motion_event.y_root = event.globalY; + motion_event.state = modifier_state; + motion_event.is_hint = NotifyNormal; + motion_event.same_screen = True; + break; + } + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: { + if (event.type == WebInputEvent::MouseEnter) { + np_event->type = EnterNotify; + } else { + np_event->type = LeaveNotify; + } + XCrossingEvent &crossing_event = np_event->xcrossing; + crossing_event.root = root; + crossing_event.time = timestamp; + crossing_event.x = event.x; + crossing_event.y = event.y; + crossing_event.x_root = event.globalX; + crossing_event.y_root = event.globalY; + crossing_event.mode = -1; // This is what Firefox sets it to. + crossing_event.detail = NotifyDetailNone; + crossing_event.same_screen = True; + // TODO(piman@google.com): set this to the correct value. Firefox does. I + // don't know where to get the information though, we get focus + // notifications, but no unfocus. + crossing_event.focus = 0; + crossing_event.state = modifier_state; + break; + } + case WebInputEvent::MouseUp: + case WebInputEvent::MouseDown: { + if (event.type == WebInputEvent::MouseDown) { + np_event->type = ButtonPress; + } else { + np_event->type = ButtonRelease; + } + XButtonEvent &button_event = np_event->xbutton; + button_event.root = root; + button_event.time = timestamp; + button_event.x = event.x; + button_event.y = event.y; + button_event.x_root = event.globalX; + button_event.y_root = event.globalY; + button_event.state = modifier_state; + switch (event.button) { + case WebMouseEvent::ButtonLeft: + button_event.button = Button1; + break; + case WebMouseEvent::ButtonMiddle: + button_event.button = Button2; + break; + case WebMouseEvent::ButtonRight: + button_event.button = Button3; + break; + default: + NOTREACHED(); + } + button_event.same_screen = True; + break; + } + default: + NOTREACHED(); + return false; + } + return true; +} + +static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, + Time timestamp, + NPEvent *np_event) { + np_event->xany.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + + switch (event.type) { + case WebKeyboardEvent::KeyDown: + np_event->type = KeyPress; + break; + case WebKeyboardEvent::KeyUp: + np_event->type = KeyRelease; + break; + default: + NOTREACHED(); + return false; + } + XKeyEvent &key_event = np_event->xkey; + key_event.send_event = False; + key_event.display = GDK_DISPLAY(); + // NOTE: Firefox keeps xany.serial and xany.window as 0. + // TODO(piman@google.com): is this right for multiple screens ? + key_event.root = DefaultRootWindow(key_event.display); + key_event.time = timestamp; + // NOTE: We don't have the correct information for x/y/x_root/y_root. Firefox + // doesn't have it either, so we pass the same values. + key_event.x = 0; + key_event.y = 0; + key_event.x_root = -1; + key_event.y_root = -1; + key_event.state = GetXModifierState(event.modifiers); + key_event.keycode = event.nativeKeyCode; + key_event.same_screen = True; + return true; +} + +static bool NPEventFromWebInputEvent(const WebInputEvent& event, + Time timestamp, + NPEvent* np_event) { + switch (event.type) { + case WebInputEvent::MouseMove: + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + case WebInputEvent::MouseDown: + case WebInputEvent::MouseUp: + if (event.size < sizeof(WebMouseEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebMouseEvent( + *static_cast<const WebMouseEvent*>(&event), timestamp, np_event); + case WebInputEvent::KeyDown: + case WebInputEvent::KeyUp: + if (event.size < sizeof(WebKeyboardEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&event), timestamp, np_event); + default: + return false; + } +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + + if (first_event_time_ < 0.0) + first_event_time_ = event.timeStampSeconds; + Time timestamp = static_cast<Time>( + (event.timeStampSeconds - first_event_time_) * 1.0e3); + NPEvent np_event = {0}; + if (!NPEventFromWebInputEvent(event, timestamp, &np_event)) { + return false; + } + // See comment about PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK in constructor. + if (windowless_ && + (quirks_ & PLUGIN_QUIRK_WINDOWLESS_NO_RIGHT_CLICK) && + (np_event.type == ButtonPress || np_event.type == ButtonRelease) && + (np_event.xbutton.button == Button3)) { + return false; + } + + bool ret = instance()->NPP_HandleEvent(&np_event) != 0; + + // Flash always returns false, even when the event is handled. + ret = true; + +#if 0 + if (event->event == WM_MOUSEMOVE) { + // Snag a reference to the current cursor ASAP in case the plugin modified + // it. There is a nasty race condition here with the multiprocess browser + // as someone might be setting the cursor in the main process as well. + *cursor = current_windowless_cursor_; + } +#endif + + return ret; +} diff --git a/webkit/glue/plugins/webplugin_delegate_impl_mac.mm b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm new file mode 100644 index 0000000..efa6bdd --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm @@ -0,0 +1,1160 @@ +// Copyright (c) 2010 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. + +#import <Cocoa/Cocoa.h> +#import <QuartzCore/QuartzCore.h> + +#include "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <string> +#include <unistd.h> +#include <set> + +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/plugin_web_event_converter_mac.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +#ifndef NP_NO_CARBON +#include "webkit/glue/plugins/carbon_plugin_window_tracker_mac.h" +#endif + +#ifndef NP_NO_QUICKDRAW +#include "webkit/glue/plugins/quickdraw_drawing_manager_mac.h" +#endif + +using webkit_glue::WebPlugin; +using webkit_glue::WebPluginDelegate; +using webkit_glue::WebPluginResourceClient; +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebMouseWheelEvent; + +const int kCoreAnimationRedrawPeriodMs = 10; // 100 Hz + +// Important implementation notes: The Mac definition of NPAPI, particularly +// the distinction between windowed and windowless modes, differs from the +// Windows and Linux definitions. Most of those differences are +// accomodated by the WebPluginDelegate class. + +namespace { + +WebPluginDelegateImpl* g_active_delegate; + +// Helper to simplify correct usage of g_active_delegate. Instantiating will +// set the active delegate to |delegate| for the lifetime of the object, then +// NULL when it goes out of scope. +class ScopedActiveDelegate { +public: + explicit ScopedActiveDelegate(WebPluginDelegateImpl* delegate) { + g_active_delegate = delegate; + } + ~ScopedActiveDelegate() { + g_active_delegate = NULL; + } +private: + DISALLOW_COPY_AND_ASSIGN(ScopedActiveDelegate); +}; + +#ifndef NP_NO_CARBON +// Timer periods for sending idle events to Carbon plugins. The visible value +// (50Hz) matches both Safari and Firefox. The hidden value (8Hz) matches +// Firefox; according to https://bugzilla.mozilla.org/show_bug.cgi?id=525533 +// going lower than that causes issues. +const int kVisibleIdlePeriodMs = 20; // (50Hz) +const int kHiddenIdlePeriodMs = 125; // (8Hz) + +class CarbonIdleEventSource { + public: + // Returns the shared Carbon idle event source. + static CarbonIdleEventSource* SharedInstance() { + DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); + static CarbonIdleEventSource* event_source = new CarbonIdleEventSource(); + return event_source; + } + + // Registers the plugin delegate as interested in receiving idle events at + // a rate appropriate for the given visibility. A delegate can safely be + // re-registered any number of times, with the latest registration winning. + void RegisterDelegate(WebPluginDelegateImpl* delegate, bool visible) { + if (visible) { + visible_delegates_->RegisterDelegate(delegate); + hidden_delegates_->UnregisterDelegate(delegate); + } else { + hidden_delegates_->RegisterDelegate(delegate); + visible_delegates_->UnregisterDelegate(delegate); + } + } + + // Removes the plugin delegate from the list of plugins receiving idle events. + void UnregisterDelegate(WebPluginDelegateImpl* delegate) { + visible_delegates_->UnregisterDelegate(delegate); + hidden_delegates_->UnregisterDelegate(delegate); + } + + private: + class VisibilityGroup { + public: + explicit VisibilityGroup(int timer_period) + : timer_period_(timer_period), iterator_(delegates_.end()) {} + + // Adds |delegate| to this visibility group. + void RegisterDelegate(WebPluginDelegateImpl* delegate) { + if (delegates_.empty()) { + timer_.Start(base::TimeDelta::FromMilliseconds(timer_period_), + this, &VisibilityGroup::SendIdleEvents); + } + delegates_.insert(delegate); + } + + // Removes |delegate| from this visibility group. + void UnregisterDelegate(WebPluginDelegateImpl* delegate) { + // If a plugin changes visibility during idle event handling, it + // may be removed from this set while SendIdleEvents is still iterating; + // if that happens and it's next on the list, increment the iterator + // before erasing so that the iteration won't be corrupted. + if ((iterator_ != delegates_.end()) && (*iterator_ == delegate)) + ++iterator_; + size_t removed = delegates_.erase(delegate); + if (removed > 0 && delegates_.empty()) + timer_.Stop(); + } + + private: + // Fires off idle events for each delegate in the group. + void SendIdleEvents() { + for (iterator_ = delegates_.begin(); iterator_ != delegates_.end();) { + // Pre-increment so that the skip logic in UnregisterDelegates works. + WebPluginDelegateImpl* delegate = *(iterator_++); + delegate->FireIdleEvent(); + } + } + + int timer_period_; + base::RepeatingTimer<VisibilityGroup> timer_; + std::set<WebPluginDelegateImpl*> delegates_; + std::set<WebPluginDelegateImpl*>::iterator iterator_; + }; + + CarbonIdleEventSource() + : visible_delegates_(new VisibilityGroup(kVisibleIdlePeriodMs)), + hidden_delegates_(new VisibilityGroup(kHiddenIdlePeriodMs)) {} + + scoped_ptr<VisibilityGroup> visible_delegates_; + scoped_ptr<VisibilityGroup> hidden_delegates_; + + DISALLOW_COPY_AND_ASSIGN(CarbonIdleEventSource); +}; +#endif // !NP_NO_CARBON + +} // namespace + +// Helper to build and maintain a model of a drag entering the plugin but not +// starting there. See explanation in PlatformHandleInputEvent. +class ExternalDragTracker { + public: + ExternalDragTracker() : pressed_buttons_(0) {} + + // Returns true if an external drag is in progress. + bool IsDragInProgress() { return pressed_buttons_ != 0; }; + + // Returns true if the given event appears to be related to an external drag. + bool EventIsRelatedToDrag(const WebInputEvent& event); + + // Updates the tracking of whether an external drag is in progress--and if + // so what buttons it involves--based on the given event. + void UpdateDragStateFromEvent(const WebInputEvent& event); + + private: + // Returns the mask for just the button state in a WebInputEvent's modifiers. + static int WebEventButtonModifierMask(); + + // The WebInputEvent modifier flags for any buttons that were down when an + // external drag entered the plugin, and which and are still down now. + int pressed_buttons_; + + DISALLOW_COPY_AND_ASSIGN(ExternalDragTracker); +}; + +void ExternalDragTracker::UpdateDragStateFromEvent(const WebInputEvent& event) { + switch (event.type) { + case WebInputEvent::MouseEnter: + pressed_buttons_ = event.modifiers & WebEventButtonModifierMask(); + break; + case WebInputEvent::MouseUp: { + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + if (mouse_event->button == WebMouseEvent::ButtonLeft) + pressed_buttons_ &= ~WebInputEvent::LeftButtonDown; + if (mouse_event->button == WebMouseEvent::ButtonMiddle) + pressed_buttons_ &= ~WebInputEvent::MiddleButtonDown; + if (mouse_event->button == WebMouseEvent::ButtonRight) + pressed_buttons_ &= ~WebInputEvent::RightButtonDown; + break; + } + default: + break; + } +} + +bool ExternalDragTracker::EventIsRelatedToDrag(const WebInputEvent& event) { + const WebMouseEvent* mouse_event = static_cast<const WebMouseEvent*>(&event); + switch (event.type) { + case WebInputEvent::MouseUp: + // We only care about release of buttons that were part of the drag. + return ((mouse_event->button == WebMouseEvent::ButtonLeft && + (pressed_buttons_ & WebInputEvent::LeftButtonDown)) || + (mouse_event->button == WebMouseEvent::ButtonMiddle && + (pressed_buttons_ & WebInputEvent::MiddleButtonDown)) || + (mouse_event->button == WebMouseEvent::ButtonRight && + (pressed_buttons_ & WebInputEvent::RightButtonDown))); + case WebInputEvent::MouseEnter: + return (event.modifiers & WebEventButtonModifierMask()) != 0; + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseMove: { + int event_buttons = (event.modifiers & WebEventButtonModifierMask()); + return (pressed_buttons_ && + pressed_buttons_ == event_buttons); + } + default: + return false; + } + return false; +} + +int ExternalDragTracker::WebEventButtonModifierMask() { + return WebInputEvent::LeftButtonDown | + WebInputEvent::RightButtonDown | + WebInputEvent::MiddleButtonDown; +} + +#pragma mark - +#pragma mark Core WebPluginDelegate implementation + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : windowed_handle_(NULL), + // all Mac plugins are "windowless" in the Windows/X11 sense + windowless_(true), + plugin_(NULL), + instance_(instance), + parent_(containing_view), + quirks_(0), + buffer_context_(NULL), + layer_(nil), + surface_(NULL), + renderer_(nil), + plugin_has_focus_(false), + has_webkit_focus_(false), + containing_view_has_focus_(false), + containing_window_has_focus_(false), + initial_window_focus_(false), + container_is_visible_(false), + have_called_set_window_(false), + external_drag_tracker_(new ExternalDragTracker()), + handle_event_depth_(0), + first_set_window_call_(true) { + memset(&window_, 0, sizeof(window_)); +#ifndef NP_NO_CARBON + memset(&np_cg_context_, 0, sizeof(np_cg_context_)); +#endif +#ifndef NP_NO_QUICKDRAW + memset(&qd_port_, 0, sizeof(qd_port_)); +#endif + instance->set_windowless(true); +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + DestroyInstance(); + +#ifndef NP_NO_CARBON + if (np_cg_context_.window) { + CarbonPluginWindowTracker::SharedInstance()->DestroyDummyWindowForDelegate( + this, reinterpret_cast<WindowRef>(np_cg_context_.window)); + } +#endif +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + // Don't set a NULL window handle on destroy for Mac plugins. This matches + // Safari and other Mac browsers (see PluginView::stop() in PluginView.cpp, + // where code to do so is surrounded by an #ifdef that excludes Mac OS X, or + // destroyPlugin in WebNetscapePluginView.mm, for examples). + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; + + // Mac plugins don't expect to be unloaded, and they don't always do so + // cleanly, so don't unload them at shutdown. + instance()->plugin_lib()->PreventLibraryUnload(); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + // For some QuickDraw plugins, we can sometimes get away with giving them + // a port pointing to a pixel buffer instead of a our actual dummy window. + // This gives us much better frame rates, because the window scraping we + // normally use is very slow. + // This breaks down if the plugin does anything complicated with the port + // (as QuickTime seems to during event handling, and sometimes when painting + // its controls), so we switch on the fly as necessary. (It might be + // possible to interpose sufficiently that we wouldn't have to switch back + // and forth, but the current approach gets us most of the benefit.) + // We can't do this at all with plugins that bypass the port entirely and + // attaches their own surface to the window. + // TODO(stuartmorgan): Test other QuickDraw plugins that we support and + // see if any others can use the fast path. + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + if (plugin_info.name.find(ASCIIToUTF16("QuickTime")) != string16::npos) + quirks_ |= PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH; + } +#endif + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + // Create a stand-in for the browser window so that the plugin will have + // a non-NULL WindowRef to which it can refer. + CarbonPluginWindowTracker* window_tracker = + CarbonPluginWindowTracker::SharedInstance(); + np_cg_context_.window = window_tracker->CreateDummyWindowForDelegate(this); + np_cg_context_.context = NULL; + UpdateDummyWindowBounds(gfx::Point(0, 0)); + } +#endif + + NPDrawingModel drawing_model = instance()->drawing_model(); + switch (drawing_model) { +#ifndef NP_NO_QUICKDRAW + case NPDrawingModelQuickDraw: + if (instance()->event_model() != NPEventModelCarbon) + return false; + qd_manager_.reset(new QuickDrawDrawingManager()); + qd_manager_->SetPluginWindow( + reinterpret_cast<WindowRef>(np_cg_context_.window)); + qd_port_.port = qd_manager_->port(); + window_.window = &qd_port_; + window_.type = NPWindowTypeDrawable; + break; +#endif + case NPDrawingModelCoreGraphics: +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + window_.window = &np_cg_context_; +#endif + window_.type = NPWindowTypeDrawable; + break; + case NPDrawingModelCoreAnimation: + case NPDrawingModelInvalidatingCoreAnimation: { + if (instance()->event_model() != NPEventModelCocoa) + return false; + window_.type = NPWindowTypeDrawable; + // Ask the plug-in for the CALayer it created for rendering content. Have + // the renderer tell the browser to create a "windowed plugin" to host + // the IOSurface. + CALayer* layer = nil; + NPError err = instance()->NPP_GetValue(NPPVpluginCoreAnimationLayer, + reinterpret_cast<void*>(&layer)); + if (!err) { + if (drawing_model == NPDrawingModelCoreAnimation) { + // Create the timer; it will be started when we get a window handle. + redraw_timer_.reset(new base::RepeatingTimer<WebPluginDelegateImpl>); + } + layer_ = layer; + surface_ = new AcceleratedSurface; + surface_->Initialize(NULL, true); + renderer_ = [[CARenderer rendererWithCGLContext:surface_->context() + options:NULL] retain]; + [renderer_ setLayer:layer_]; + plugin_->BindFakePluginWindowHandle(false); + } + break; + } + default: + NOTREACHED(); + break; + } + + // TODO(stuartmorgan): We need real plugin container visibility information + // when the plugin is initialized; for now, assume it's visible. + // None of the calls SetContainerVisibility would make are useful at this + // point, so we just set the initial state directly. + container_is_visible_ = true; + + // Let the WebPlugin know that we are windowless (unless this is a + // Core Animation plugin, in which case BindFakePluginWindowHandle will take + // care of setting up the appropriate window handle). + if (!layer_) + plugin_->SetWindow(NULL); + +#ifndef NP_NO_CARBON + // If the plugin wants Carbon events, hook up to the source of idle events. + if (instance()->event_model() == NPEventModelCarbon) + UpdateIdleEventRate(); +#endif + + // QuickTime (in QD mode only) can crash if it gets other calls (e.g., + // NPP_Write) before it gets a SetWindow call, so call SetWindow (with a 0x0 + // rect) immediately. +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + if (plugin_info.name.find(ASCIIToUTF16("QuickTime")) != string16::npos) + WindowlessSetWindow(); + } +#endif + + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + CarbonIdleEventSource::SharedInstance()->UnregisterDelegate(this); +#endif + if (redraw_timer_.get()) + redraw_timer_->Stop(); + [renderer_ release]; + renderer_ = nil; + layer_ = nil; + if (surface_) { + surface_->Destroy(); + delete surface_; + surface_ = NULL; + } +} + +void WebPluginDelegateImpl::UpdateGeometryAndContext( + const gfx::Rect& window_rect, const gfx::Rect& clip_rect, + CGContextRef context) { + buffer_context_ = context; +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + // Update the structure that is passed to Carbon+CoreGraphics plugins in + // NPP_SetWindow before calling UpdateGeometry, since that will trigger an + // NPP_SetWindow call if the geometry changes (which is the only time the + // context would be different), and some plugins (e.g., Flash) have an + // internal cache of the context that they only update when NPP_SetWindow + // is called. + np_cg_context_.context = context; + } +#endif +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->SetTargetContext(context, window_rect.size()); +#endif + UpdateGeometry(window_rect, clip_rect); +} + +void WebPluginDelegateImpl::Paint(CGContextRef context, const gfx::Rect& rect) { + WindowlessPaint(context, rect); + +#ifndef NP_NO_QUICKDRAW + // Paint events are our cue to dump the current plugin bits into the buffer + // context if we are dealing with a QuickDraw plugin. + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + qd_manager_->UpdateContext(); + } +#endif +} + +void WebPluginDelegateImpl::Print(CGContextRef context) { + NOTIMPLEMENTED(); +} + +void WebPluginDelegateImpl::SetFocus(bool focused) { + // This is called when internal WebKit focus (the focused element on the page) + // changes, but plugins need to know about actual first responder status, so + // we have an extra layer of focus tracking. + has_webkit_focus_ = focused; + if (containing_view_has_focus_) + SetPluginHasFocus(focused); +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + DCHECK(cursor_info != NULL); + + // If we get an event before we've set up the plugin, bail. + if (!have_called_set_window_) + return false; +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon && + !np_cg_context_.context) { + return false; + } +#endif + + if (WebInputEvent::isMouseEventType(event.type) || + event.type == WebInputEvent::MouseWheel) { + // Ideally we would compute the content origin from the web event using the + // code below as a safety net for missed content area location changes. + // Because of <http://crbug.com/9996>, however, only globalX/Y are right if + // the page has been zoomed, so for now the coordinates we get aren't + // trustworthy enough to use for corrections. +#if PLUGIN_SCALING_FIXED + // Check our plugin location before we send the event to the plugin, just + // in case we somehow missed a plugin frame change. + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + gfx::Point content_origin( + mouse_event->globalX - mouse_event->x - window_rect_.x(), + mouse_event->globalY - mouse_event->y - window_rect_.y()); + if (content_origin.x() != content_area_origin_.x() || + content_origin.y() != content_area_origin_.y()) { + DLOG(WARNING) << "Stale plugin content area location: " + << content_area_origin_ << " instead of " + << content_origin; + SetContentAreaOrigin(content_origin); + } +#endif + + current_windowless_cursor_.GetCursorInfo(cursor_info); + } + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) { + if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) { + // Mouse event handling doesn't work correctly in the fast path mode, + // so any time we get a mouse event turn the fast path off, but set a + // time to switch it on again (we don't rely just on MouseLeave because + // we don't want poor performance in the case of clicking the play + // button and then leaving the mouse there). + // This isn't perfect (specifically, click-and-hold doesn't seem to work + // if the fast path is on), but the slight regression is worthwhile + // for the improved framerates. + if (WebInputEvent::isMouseEventType(event.type)) { + if (event.type == WebInputEvent::MouseLeave) { + SetQuickDrawFastPathEnabled(true); + } else { + SetQuickDrawFastPathEnabled(false); + } + // Make sure the plugin wasn't destroyed during the switch. + if (!instance()) + return false; + } + } + + qd_manager_->MakePortCurrent(); + } +#endif + + if (event.type == WebInputEvent::MouseMove) { + return true; // The recurring FireIdleEvent will send null events. + } + } +#endif + + ScopedActiveDelegate active_delegate(this); + + // Create the plugin event structure. + NPEventModel event_model = instance()->event_model(); + scoped_ptr<PluginWebEventConverter> event_converter( + PluginWebEventConverterFactory::CreateConverterForModel(event_model)); + if (!(event_converter.get() && event_converter->InitWithEvent(event))) { + // Silently consume any keyboard event types that we don't handle, so that + // they don't fall through to the page. + if (WebInputEvent::isKeyboardEventType(event.type)) + return true; + return false; + } + void* plugin_event = event_converter->plugin_event(); + + if (instance()->event_model() == NPEventModelCocoa) { + // We recieve events related to drags starting outside the plugin, but the + // NPAPI Cocoa event model spec says plugins shouldn't receive them, so + // filter them out. + // If we add a page capture mode at the WebKit layer (like the plugin + // capture mode that handles drags starting inside) this can be removed. + bool drag_related = external_drag_tracker_->EventIsRelatedToDrag(event); + external_drag_tracker_->UpdateDragStateFromEvent(event); + if (drag_related) { + if (event.type == WebInputEvent::MouseUp && + !external_drag_tracker_->IsDragInProgress()) { + // When an external drag ends, we need to synthesize a MouseEntered. + NPCocoaEvent enter_event = *(static_cast<NPCocoaEvent*>(plugin_event)); + enter_event.type = NPCocoaEventMouseEntered; + NPAPI::ScopedCurrentPluginEvent event_scope(instance(), &enter_event); + instance()->NPP_HandleEvent(&enter_event); + } + return false; + } + } + +#ifndef PLUGIN_SCALING_FIXED + // Because of <http://crbug.com/9996>, the non-global coordinates we get for + // zoomed pages are wrong. As a temporary hack around that bug, override the + // coordinates we are given with ones computed based on our knowledge of where + // the plugin is on screen. We only need to do this for Cocoa, since Carbon + // only uses the global coordinates. + if (instance()->event_model() == NPEventModelCocoa && + (WebInputEvent::isMouseEventType(event.type) || + event.type == WebInputEvent::MouseWheel)) { + const WebMouseEvent* mouse_event = + static_cast<const WebMouseEvent*>(&event); + NPCocoaEvent* cocoa_event = static_cast<NPCocoaEvent*>(plugin_event); + cocoa_event->data.mouse.pluginX = + mouse_event->globalX - content_area_origin_.x() - window_rect_.x(); + cocoa_event->data.mouse.pluginY = + mouse_event->globalY - content_area_origin_.y() - window_rect_.y(); + } +#endif + + // Send the plugin the event. + scoped_ptr<NPAPI::ScopedCurrentPluginEvent> event_scope(NULL); + if (instance()->event_model() == NPEventModelCocoa) { + event_scope.reset(new NPAPI::ScopedCurrentPluginEvent( + instance(), static_cast<NPCocoaEvent*>(plugin_event))); + } + bool handled = instance()->NPP_HandleEvent(plugin_event) != 0; + + // Plugins don't give accurate information about whether or not they handled + // events, so browsers on the Mac ignore the return value. + // Scroll events are the exception, since the Cocoa spec defines a meaning + // for the return value. + if (WebInputEvent::isMouseEventType(event.type)) { + handled = true; + } else if (WebInputEvent::isKeyboardEventType(event.type)) { + // For Command-key events, trust the return value since eating all menu + // shortcuts is not ideal. + // TODO(stuartmorgan): Implement the advanced key handling spec, and trust + // trust the key event return value from plugins that implement it. + if (!(event.modifiers & WebInputEvent::MetaKey)) + handled = true; + } + + return handled; +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NOTIMPLEMENTED(); +} + +#pragma mark - + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + gfx::Rect old_clip_rect = clip_rect_; + cached_clip_rect_ = clip_rect; + if (container_is_visible_) // Remove check when cached_clip_rect_ is removed. + clip_rect_ = clip_rect; + bool clip_rect_changed = (clip_rect_ != old_clip_rect); + bool window_size_changed = (window_rect.size() != window_rect_.size()); + + bool force_set_window = false; +#ifndef NP_NO_QUICKDRAW + // In a QuickDraw plugin, a geometry update might have caused a port change; + // if so, we need to call SetWindow even if nothing else changed. + if (qd_manager_.get() && (qd_port_.port != qd_manager_->port())) { + qd_port_.port = qd_manager_->port(); + force_set_window = true; + } +#endif + + if (window_rect == window_rect_ && !clip_rect_changed && !force_set_window) + return; + + if (old_clip_rect.IsEmpty() != clip_rect_.IsEmpty()) { + PluginVisibilityChanged(); + } + + SetPluginRect(window_rect); + +#ifndef NP_NO_QUICKDRAW + if (window_size_changed && qd_manager_.get() && + qd_manager_->IsFastPathEnabled()) { + // If the window size has changed, we need to turn off the fast path so that + // the full redraw goes to the window and we get a correct baseline paint. + SetQuickDrawFastPathEnabled(false); + return; // SetQuickDrawFastPathEnabled will call SetWindow for us. + } +#endif + + if (window_size_changed || clip_rect_changed || force_set_window) + WindowlessSetWindow(); +} + +void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context, + const gfx::Rect& damage_rect) { + // If we get a paint event before we are completely set up (e.g., a nested + // call while the plugin is still in NPP_SetWindow), bail. + if (!have_called_set_window_ || !buffer_context_) + return; + DCHECK(buffer_context_ == context); + + static StatsRate plugin_paint("Plugin.Paint"); + StatsScope<StatsRate> scope(plugin_paint); + + // Plugin invalidates trigger asynchronous paints with the original + // invalidation rect; the plugin may be resized before the paint is handled, + // so we need to ensure that the damage rect is still sane. + const gfx::Rect paint_rect(damage_rect.Intersect( + gfx::Rect(0, 0, window_rect_.width(), window_rect_.height()))); + + ScopedActiveDelegate active_delegate(this); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->MakePortCurrent(); +#endif + + CGContextSaveGState(context); + + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent paint_event = { 0 }; + paint_event.what = updateEvt; + paint_event.message = reinterpret_cast<uint32>(np_cg_context_.window); + paint_event.when = TickCount(); + instance()->NPP_HandleEvent(&paint_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent paint_event; + memset(&paint_event, 0, sizeof(NPCocoaEvent)); + paint_event.type = NPCocoaEventDrawRect; + paint_event.data.draw.context = context; + paint_event.data.draw.x = paint_rect.x(); + paint_event.data.draw.y = paint_rect.y(); + paint_event.data.draw.width = paint_rect.width(); + paint_event.data.draw.height = paint_rect.height(); + instance()->NPP_HandleEvent(&paint_event); + break; + } + } + + // The backing buffer can change during the call to NPP_HandleEvent, in which + // case the old context is (or is about to be) invalid. + if (context == buffer_context_) + CGContextRestoreGState(context); +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + window_.x = 0; + window_.y = 0; + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + + NPError err = instance()->NPP_SetWindow(&window_); + + // Send an appropriate window focus event after the first SetWindow. + if (!have_called_set_window_) { + have_called_set_window_ = true; + SetWindowHasFocus(initial_window_focus_); + } + +#ifndef NP_NO_QUICKDRAW + if ((quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) && + !qd_manager_->IsFastPathEnabled() && !clip_rect_.IsEmpty()) { + // Give the plugin a few seconds to stabilize so we get a good initial paint + // to use as a baseline, then switch to the fast path. + fast_path_enable_tick_ = base::TimeTicks::Now() + + base::TimeDelta::FromSeconds(3); + } +#endif + + DCHECK(err == NPERR_NO_ERROR); +} + +#pragma mark - + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + NOTREACHED(); + return false; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + NOTREACHED(); +} + +bool WebPluginDelegateImpl::WindowedReposition(const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + NOTREACHED(); + return false; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + NOTREACHED(); +} + +#pragma mark - +#pragma mark Mac Extensions + +void WebPluginDelegateImpl::PluginDidInvalidate() { + if (instance()->drawing_model() == NPDrawingModelInvalidatingCoreAnimation) + DrawLayerInSurface(); +} + +WebPluginDelegateImpl* WebPluginDelegateImpl::GetActiveDelegate() { + return g_active_delegate; +} + +void WebPluginDelegateImpl::SetWindowHasFocus(bool has_focus) { + // If we get a window focus event before calling SetWindow, just remember the + // states (WindowlessSetWindow will then send it on the first call). + if (!have_called_set_window_) { + initial_window_focus_ = has_focus; + return; + } + + if (has_focus == containing_window_has_focus_) + return; + containing_window_has_focus_ = has_focus; + +#ifndef NP_NO_QUICKDRAW + // Make sure controls repaint with the correct look. + if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) + SetQuickDrawFastPathEnabled(false); +#endif + + ScopedActiveDelegate active_delegate(this); + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent focus_event = { 0 }; + focus_event.what = activateEvt; + if (has_focus) + focus_event.modifiers |= activeFlag; + focus_event.message = + reinterpret_cast<unsigned long>(np_cg_context_.window); + focus_event.when = TickCount(); + instance()->NPP_HandleEvent(&focus_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent focus_event; + memset(&focus_event, 0, sizeof(focus_event)); + focus_event.type = NPCocoaEventWindowFocusChanged; + focus_event.data.focus.hasFocus = has_focus; + instance()->NPP_HandleEvent(&focus_event); + break; + } + } +} + +void WebPluginDelegateImpl::SetPluginHasFocus(bool has_focus) { + if (!have_called_set_window_) + return; + + if (has_focus == plugin_has_focus_) + return; + plugin_has_focus_ = has_focus; + + ScopedActiveDelegate active_delegate(this); + + switch (instance()->event_model()) { +#ifndef NP_NO_CARBON + case NPEventModelCarbon: { + NPEvent focus_event = { 0 }; + if (plugin_has_focus_) + focus_event.what = NPEventType_GetFocusEvent; + else + focus_event.what = NPEventType_LoseFocusEvent; + focus_event.when = TickCount(); + instance()->NPP_HandleEvent(&focus_event); + break; + } +#endif + case NPEventModelCocoa: { + NPCocoaEvent focus_event; + memset(&focus_event, 0, sizeof(focus_event)); + focus_event.type = NPCocoaEventFocusChanged; + focus_event.data.focus.hasFocus = plugin_has_focus_; + instance()->NPP_HandleEvent(&focus_event); + break; + } + } +} + +void WebPluginDelegateImpl::SetContentAreaHasFocus(bool has_focus) { + containing_view_has_focus_ = has_focus; + SetPluginHasFocus(containing_view_has_focus_ && has_webkit_focus_); +} + +void WebPluginDelegateImpl::SetContainerVisibility(bool is_visible) { + if (is_visible == container_is_visible_) + return; + container_is_visible_ = is_visible; + + // TODO(stuartmorgan): This is a temporary workarond for + // <http://crbug.com/34266>. When that is fixed, the cached_clip_rect_ code + // should all be removed. + if (is_visible) { + clip_rect_ = cached_clip_rect_; + } else { + clip_rect_.set_width(0); + clip_rect_.set_height(0); + } + + // If the plugin is changing visibility, let the plugin know. If it's scrolled + // off screen (i.e., cached_clip_rect_ is empty), then container visibility + // doesn't change anything. + if (!cached_clip_rect_.IsEmpty()) { + PluginVisibilityChanged(); + WindowlessSetWindow(); + } + + // When the plugin become visible, send an empty invalidate. If there were any + // pending invalidations this will trigger a paint event for the damaged + // region, and if not it's a no-op. This is necessary since higher levels + // that would normally do this weren't responsible for the clip_rect_ change). + if (!clip_rect_.IsEmpty()) + instance()->webplugin()->InvalidateRect(gfx::Rect()); +} + +void WebPluginDelegateImpl::WindowFrameChanged(gfx::Rect window_frame, + gfx::Rect view_frame) { + instance()->set_window_frame(window_frame); + SetContentAreaOrigin(gfx::Point(view_frame.x(), view_frame.y())); +} + +void WebPluginDelegateImpl::SetThemeCursor(ThemeCursor cursor) { + current_windowless_cursor_.InitFromThemeCursor(cursor); +} + +void WebPluginDelegateImpl::SetCursor(const Cursor* cursor) { + current_windowless_cursor_.InitFromCursor(cursor); +} + +void WebPluginDelegateImpl::SetNSCursor(NSCursor* cursor) { + current_windowless_cursor_.InitFromNSCursor(cursor); +} + +#pragma mark - +#pragma mark Internal Tracking + +void WebPluginDelegateImpl::SetPluginRect(const gfx::Rect& rect) { + bool plugin_size_changed = rect.width() != window_rect_.width() || + rect.height() != window_rect_.height(); + window_rect_ = rect; + PluginScreenLocationChanged(); + if (plugin_size_changed) + UpdateAcceleratedSurface(); +} + +void WebPluginDelegateImpl::SetContentAreaOrigin(const gfx::Point& origin) { + content_area_origin_ = origin; + PluginScreenLocationChanged(); +} + +void WebPluginDelegateImpl::PluginScreenLocationChanged() { + gfx::Point plugin_origin(content_area_origin_.x() + window_rect_.x(), + content_area_origin_.y() + window_rect_.y()); + instance()->set_plugin_origin(plugin_origin); + +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) { + UpdateDummyWindowBounds(plugin_origin); + } +#endif +} + +void WebPluginDelegateImpl::PluginVisibilityChanged() { +#ifndef NP_NO_CARBON + if (instance()->event_model() == NPEventModelCarbon) + UpdateIdleEventRate(); +#endif + if (instance()->drawing_model() == NPDrawingModelCoreAnimation) { + bool plugin_visible = container_is_visible_ && !clip_rect_.IsEmpty(); + if (plugin_visible && !redraw_timer_->IsRunning() && windowed_handle()) { + redraw_timer_->Start( + base::TimeDelta::FromMilliseconds(kCoreAnimationRedrawPeriodMs), + this, &WebPluginDelegateImpl::DrawLayerInSurface); + } else if (!plugin_visible) { + redraw_timer_->Stop(); + } + } +} + +#pragma mark - +#pragma mark Core Animation Support + +void WebPluginDelegateImpl::DrawLayerInSurface() { + // If we haven't plumbed up the surface yet, don't try to draw. + if (!windowed_handle()) + return; + + surface_->MakeCurrent(); + + surface_->Clear(window_rect_); + + [renderer_ beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; + if (CGRectIsEmpty([renderer_ updateBounds])) { + // If nothing has changed, we are done. + [renderer_ endFrame]; + return; + } + CGRect layerRect = [layer_ bounds]; + [renderer_ addUpdateRect:layerRect]; + [renderer_ render]; + [renderer_ endFrame]; + + surface_->SwapBuffers(); + plugin_->AcceleratedFrameBuffersDidSwap(windowed_handle()); +} + +// Update the size of the IOSurface to match the current size of the plug-in, +// then tell the browser host view so it can adjust its bookkeeping and CALayer +// appropriately. +void WebPluginDelegateImpl::UpdateAcceleratedSurface() { + // Will only have a window handle when using a Core Animation drawing model. + if (!windowed_handle() || !layer_) + return; + + [layer_ setFrame:CGRectMake(0, 0, + window_rect_.width(), window_rect_.height())]; + [renderer_ setBounds:[layer_ bounds]]; + + uint64 io_surface_id = surface_->SetSurfaceSize(window_rect_.size()); + if (io_surface_id) { + plugin_->SetAcceleratedSurface(windowed_handle(), + window_rect_.width(), + window_rect_.height(), + io_surface_id); + } +} + +void WebPluginDelegateImpl::set_windowed_handle( + gfx::PluginWindowHandle handle) { + windowed_handle_ = handle; + UpdateAcceleratedSurface(); + // Kick off the drawing timer, if necessary. + PluginVisibilityChanged(); +} + +#pragma mark - +#pragma mark Carbon Event support + +#ifndef NP_NO_CARBON +void WebPluginDelegateImpl::UpdateDummyWindowBounds( + const gfx::Point& plugin_origin) { + WindowRef window = reinterpret_cast<WindowRef>(np_cg_context_.window); + Rect current_bounds; + GetWindowBounds(window, kWindowContentRgn, ¤t_bounds); + + Rect new_bounds; + // We never want to resize the window to 0x0, so if the plugin is 0x0 just + // move the window without resizing it. + if (window_rect_.width() > 0 && window_rect_.height() > 0) { + SetRect(&new_bounds, 0, 0, window_rect_.width(), window_rect_.height()); + OffsetRect(&new_bounds, plugin_origin.x(), plugin_origin.y()); + } else { + new_bounds = current_bounds; + OffsetRect(&new_bounds, plugin_origin.x() - current_bounds.left, + plugin_origin.y() - current_bounds.top); + } + + if (new_bounds.left != current_bounds.left || + new_bounds.top != current_bounds.top || + new_bounds.right != current_bounds.right || + new_bounds.bottom != current_bounds.bottom) + SetWindowBounds(window, kWindowContentRgn, &new_bounds); +} + +void WebPluginDelegateImpl::UpdateIdleEventRate() { + bool plugin_visible = container_is_visible_ && !clip_rect_.IsEmpty(); + CarbonIdleEventSource::SharedInstance()->RegisterDelegate(this, + plugin_visible); +} + +void WebPluginDelegateImpl::FireIdleEvent() { + // Avoid a race condition between IO and UI threads during plugin shutdown + if (!instance()) + return; + // Don't send idle events until we've called SetWindow. + if (!have_called_set_window_) + return; + +#ifndef NP_NO_QUICKDRAW + // Check whether it's time to turn the QuickDraw fast path back on. + if (!fast_path_enable_tick_.is_null() && + (base::TimeTicks::Now() > fast_path_enable_tick_)) { + SetQuickDrawFastPathEnabled(true); + fast_path_enable_tick_ = base::TimeTicks(); + } +#endif + + ScopedActiveDelegate active_delegate(this); + +#ifndef NP_NO_QUICKDRAW + if (instance()->drawing_model() == NPDrawingModelQuickDraw) + qd_manager_->MakePortCurrent(); +#endif + + // Send an idle event so that the plugin can do background work + NPEvent np_event = {0}; + np_event.what = nullEvent; + np_event.when = TickCount(); + np_event.modifiers = GetCurrentKeyModifiers(); + if (!Button()) + np_event.modifiers |= btnState; + HIPoint mouse_location; + HIGetMousePosition(kHICoordSpaceScreenPixel, NULL, &mouse_location); + np_event.where.h = mouse_location.x; + np_event.where.v = mouse_location.y; + instance()->NPP_HandleEvent(&np_event); + +#ifndef NP_NO_QUICKDRAW + // Quickdraw-based plugins can draw at any time, so tell the renderer to + // repaint. + if (instance() && instance()->drawing_model() == NPDrawingModelQuickDraw) + instance()->webplugin()->Invalidate(); +#endif +} +#endif // !NP_NO_CARBON + +#pragma mark - +#pragma mark QuickDraw Support + +#ifndef NP_NO_QUICKDRAW +void WebPluginDelegateImpl::SetQuickDrawFastPathEnabled(bool enabled) { + if (!enabled) { + // Wait a couple of seconds, then turn the fast path back on. If we're + // turning it off for event handling, that ensures that the common case of + // move-mouse-then-click works (as well as making it likely that a second + // click attempt will work if the first one fails). If we're turning it + // off to force a new baseline image, this leaves plenty of time for the + // plugin to draw. + fast_path_enable_tick_ = base::TimeTicks::Now() + + base::TimeDelta::FromSeconds(2); + } + + if (enabled == qd_manager_->IsFastPathEnabled()) + return; + if (enabled && clip_rect_.IsEmpty()) { + // Don't switch to the fast path while the plugin is completely clipped; + // we can only switch when the window has an up-to-date image for us to + // scrape. We'll automatically switch after we become visible again. + return; + } + + qd_manager_->SetFastPathEnabled(enabled); + qd_port_.port = qd_manager_->port(); + WindowlessSetWindow(); + // Send a paint event so that the new buffer gets updated immediately. + WindowlessPaint(buffer_context_, clip_rect_); +} +#endif // !NP_NO_QUICKDRAW diff --git a/webkit/glue/plugins/webplugin_delegate_impl_win.cc b/webkit/glue/plugins/webplugin_delegate_impl_win.cc new file mode 100644 index 0000000..09184ab --- /dev/null +++ b/webkit/glue/plugins/webplugin_delegate_impl_win.cc @@ -0,0 +1,1397 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/plugins/webplugin_delegate_impl.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/file_util.h" +#include "base/iat_patch.h" +#include "base/lazy_instance.h" +#include "base/message_loop.h" +#include "base/registry.h" +#include "base/scoped_ptr.h" +#include "base/stats_counters.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "webkit/glue/plugins/default_plugin_shared.h" +#include "webkit/glue/plugins/plugin_constants_win.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/plugin_lib.h" +#include "webkit/glue/plugins/plugin_list.h" +#include "webkit/glue/plugins/plugin_stream_url.h" +#include "webkit/glue/plugins/webplugin.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebCursorInfo; +using WebKit::WebKeyboardEvent; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; + +namespace { + +const wchar_t kWebPluginDelegateProperty[] = L"WebPluginDelegateProperty"; +const wchar_t kPluginNameAtomProperty[] = L"PluginNameAtom"; +const wchar_t kDummyActivationWindowName[] = L"DummyWindowForActivation"; +const wchar_t kPluginFlashThrottle[] = L"FlashThrottle"; + +// The fastest we are willing to process WM_USER+1 events for Flash. +// Flash can easily exceed the limits of our CPU if we don't throttle it. +// The throttle has been chosen by testing various delays and compromising +// on acceptable Flash performance and reasonable CPU consumption. +// +// I'd like to make the throttle delay variable, based on the amount of +// time currently required to paint Flash plugins. There isn't a good +// way to count the time spent in aggregate plugin painting, however, so +// this seems to work well enough. +const int kFlashWMUSERMessageThrottleDelayMs = 5; + +// Flash displays popups in response to user clicks by posting a WM_USER +// message to the plugin window. The handler for this message displays +// the popup. To ensure that the popups allowed state is sent correctly +// to the renderer we reset the popups allowed state in a timer. +const int kWindowedPluginPopupTimerMs = 50; + +// The current instance of the plugin which entered the modal loop. +WebPluginDelegateImpl* g_current_plugin_instance = NULL; + +typedef std::deque<MSG> ThrottleQueue; +base::LazyInstance<ThrottleQueue> g_throttle_queue(base::LINKER_INITIALIZED); +base::LazyInstance<std::map<HWND, WNDPROC> > g_window_handle_proc_map( + base::LINKER_INITIALIZED); + + +// Helper object for patching the TrackPopupMenu API. +base::LazyInstance<iat_patch::IATPatchFunction> g_iat_patch_track_popup_menu( + base::LINKER_INITIALIZED); + +// Helper object for patching the SetCursor API. +base::LazyInstance<iat_patch::IATPatchFunction> g_iat_patch_set_cursor( + base::LINKER_INITIALIZED); + +// Helper object for patching the RegEnumKeyExW API. +base::LazyInstance<iat_patch::IATPatchFunction> g_iat_patch_reg_enum_key_ex_w( + base::LINKER_INITIALIZED); + +// http://crbug.com/16114 +// Enforces providing a valid device context in NPWindow, so that NPP_SetWindow +// is never called with NPNWindoTypeDrawable and NPWindow set to NULL. +// Doing so allows removing NPP_SetWindow call during painting a windowless +// plugin, which otherwise could trigger layout change while painting by +// invoking NPN_Evaluate. Which would cause bad, bad crashes. Bad crashes. +// TODO(dglazkov): If this approach doesn't produce regressions, move class to +// webplugin_delegate_impl.h and implement for other platforms. +class DrawableContextEnforcer { + public: + explicit DrawableContextEnforcer(NPWindow* window) + : window_(window), + disposable_dc_(window && !window->window) { + // If NPWindow is NULL, create a device context with monochrome 1x1 surface + // and stuff it to NPWindow. + if (disposable_dc_) + window_->window = CreateCompatibleDC(NULL); + } + + ~DrawableContextEnforcer() { + if (!disposable_dc_) + return; + + DeleteDC(static_cast<HDC>(window_->window)); + window_->window = NULL; + } + + private: + NPWindow* window_; + bool disposable_dc_; +}; + +// These are from ntddk.h +typedef LONG NTSTATUS; + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +#ifndef STATUS_BUFFER_TOO_SMALL +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#endif + +typedef enum _KEY_INFORMATION_CLASS { + KeyBasicInformation, + KeyNodeInformation, + KeyFullInformation, + KeyNameInformation, + KeyCachedInformation, + KeyVirtualizationInformation +} KEY_INFORMATION_CLASS; + +typedef struct _KEY_NAME_INFORMATION { + ULONG NameLength; + WCHAR Name[1]; +} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION; + +typedef DWORD (__stdcall *ZwQueryKeyType)( + HANDLE key_handle, + int key_information_class, + PVOID key_information, + ULONG length, + PULONG result_length); + +// Returns a key's full path. +std::wstring GetKeyPath(HKEY key) { + if (key == NULL) + return L""; + + HMODULE dll = GetModuleHandle(L"ntdll.dll"); + if (dll == NULL) + return L""; + + ZwQueryKeyType func = reinterpret_cast<ZwQueryKeyType>( + ::GetProcAddress(dll, "ZwQueryKey")); + if (func == NULL) + return L""; + + DWORD size = 0; + DWORD result = 0; + result = func(key, KeyNameInformation, 0, 0, &size); + if (result != STATUS_BUFFER_TOO_SMALL) + return L""; + + scoped_array<char> buffer(new char[size]); + if (buffer.get() == NULL) + return L""; + + result = func(key, KeyNameInformation, buffer.get(), size, &size); + if (result != STATUS_SUCCESS) + return L""; + + KEY_NAME_INFORMATION* info = + reinterpret_cast<KEY_NAME_INFORMATION*>(buffer.get()); + return std::wstring(info->Name, info->NameLength / sizeof(wchar_t)); +} + +} // namespace + +bool WebPluginDelegateImpl::IsPluginDelegateWindow(HWND window) { + // We use a buffer that is one char longer than we need to detect cases where + // kNativeWindowClassName is a prefix of the given window's class name. It + // happens that GetClassNameW will just silently truncate the class name to + // fit into the given buffer. + wchar_t class_name[arraysize(kNativeWindowClassName) + 1]; + if (!GetClassNameW(window, class_name, arraysize(class_name))) + return false; + return wcscmp(class_name, kNativeWindowClassName) == 0; +} + +bool WebPluginDelegateImpl::GetPluginNameFromWindow( + HWND window, std::wstring *plugin_name) { + if (NULL == plugin_name) { + return false; + } + if (!IsPluginDelegateWindow(window)) { + return false; + } + ATOM plugin_name_atom = reinterpret_cast<ATOM>( + GetPropW(window, kPluginNameAtomProperty)); + if (plugin_name_atom != 0) { + WCHAR plugin_name_local[MAX_PATH] = {0}; + GlobalGetAtomNameW(plugin_name_atom, + plugin_name_local, + ARRAYSIZE(plugin_name_local)); + *plugin_name = plugin_name_local; + return true; + } + return false; +} + +bool WebPluginDelegateImpl::IsDummyActivationWindow(HWND window) { + if (!IsWindow(window)) + return false; + + wchar_t window_title[MAX_PATH + 1] = {0}; + if (GetWindowText(window, window_title, arraysize(window_title))) { + return (0 == lstrcmpiW(window_title, kDummyActivationWindowName)); + } + return false; +} + +LRESULT CALLBACK WebPluginDelegateImpl::HandleEventMessageFilterHook( + int code, WPARAM wParam, LPARAM lParam) { + if (g_current_plugin_instance) { + g_current_plugin_instance->OnModalLoopEntered(); + } else { + NOTREACHED(); + } + return CallNextHookEx(NULL, code, wParam, lParam); +} + +LRESULT CALLBACK WebPluginDelegateImpl::MouseHookProc( + int code, WPARAM wParam, LPARAM lParam) { + if (code == HC_ACTION) { + MOUSEHOOKSTRUCT* hook_struct = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam); + if (hook_struct) + HandleCaptureForMessage(hook_struct->hwnd, wParam); + } + + return CallNextHookEx(NULL, code, wParam, lParam); +} + +WebPluginDelegateImpl::WebPluginDelegateImpl( + gfx::PluginWindowHandle containing_view, + NPAPI::PluginInstance *instance) + : parent_(containing_view), + instance_(instance), + quirks_(0), + plugin_(NULL), + windowless_(false), + windowed_handle_(NULL), + windowed_did_set_window_(false), + plugin_wnd_proc_(NULL), + last_message_(0), + is_calling_wndproc(false), + keyboard_layout_(NULL), + parent_thread_id_(0), + dummy_window_for_activation_(NULL), + handle_event_message_filter_hook_(NULL), + handle_event_pump_messages_event_(NULL), + user_gesture_message_posted_(false), +#pragma warning(suppress: 4355) // can use this + user_gesture_msg_factory_(this), + handle_event_depth_(0), + mouse_hook_(NULL), + first_set_window_call_(true) { + memset(&window_, 0, sizeof(window_)); + + const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info(); + std::wstring filename = + StringToLowerASCII(plugin_info.path.BaseName().value()); + + if (instance_->mime_type() == "application/x-shockwave-flash" || + filename == kFlashPlugin) { + // Flash only requests windowless plugins if we return a Mozilla user + // agent. + instance_->set_use_mozilla_user_agent(); + quirks_ |= PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE; + quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; + quirks_ |= PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS; + quirks_ |= PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE; + } else if (filename == kAcrobatReaderPlugin) { + // Check for the version number above or equal 9. + std::vector<std::wstring> version; + SplitString(plugin_info.version, L'.', &version); + if (version.size() > 0) { + int major = static_cast<int>(StringToInt64(version[0])); + if (major >= 9) { + quirks_ |= PLUGIN_QUIRK_DIE_AFTER_UNLOAD; + + // 9.2 needs this. + quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; + } + } + quirks_ |= PLUGIN_QUIRK_BLOCK_NONSTANDARD_GETURL_REQUESTS; + } else if (plugin_info.name.find(L"Windows Media Player") != + std::wstring::npos) { + // Windows Media Player needs two NPP_SetWindow calls. + quirks_ |= PLUGIN_QUIRK_SETWINDOW_TWICE; + + // Windowless mode doesn't work in the WMP NPAPI plugin. + quirks_ |= PLUGIN_QUIRK_NO_WINDOWLESS; + + // The media player plugin sets its size on the first NPP_SetWindow call + // and never updates its size. We should call the underlying NPP_SetWindow + // only when we have the correct size. + quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; + + if (filename == kOldWMPPlugin) { + // Non-admin users on XP couldn't modify the key to force the new UI. + quirks_ |= PLUGIN_QUIRK_PATCH_REGENUMKEYEXW; + } + } else if (instance_->mime_type() == "audio/x-pn-realaudio-plugin" || + filename == kRealPlayerPlugin) { + quirks_ |= PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY; + } else if (plugin_info.name.find(L"VLC Multimedia Plugin") != + std::wstring::npos || + plugin_info.name.find(L"VLC Multimedia Plug-in") != + std::wstring::npos) { + // VLC hangs on NPP_Destroy if we call NPP_SetWindow with a null window + // handle + quirks_ |= PLUGIN_QUIRK_DONT_SET_NULL_WINDOW_HANDLE_ON_DESTROY; + // VLC 0.8.6d and 0.8.6e crash if multiple instances are created. + quirks_ |= PLUGIN_QUIRK_DONT_ALLOW_MULTIPLE_INSTANCES; + } else if (filename == kSilverlightPlugin) { + // Explanation for this quirk can be found in + // WebPluginDelegateImpl::Initialize. + quirks_ |= PLUGIN_QUIRK_PATCH_SETCURSOR; + } else if (plugin_info.name.find(L"DivX Web Player") != + std::wstring::npos) { + // The divx plugin sets its size on the first NPP_SetWindow call and never + // updates its size. We should call the underlying NPP_SetWindow only when + // we have the correct size. + quirks_ |= PLUGIN_QUIRK_IGNORE_FIRST_SETWINDOW_CALL; + } +} + +WebPluginDelegateImpl::~WebPluginDelegateImpl() { + if (::IsWindow(dummy_window_for_activation_)) { + ::DestroyWindow(dummy_window_for_activation_); + } + + DestroyInstance(); + + if (!windowless_) + WindowedDestroyWindow(); + + if (handle_event_pump_messages_event_) { + CloseHandle(handle_event_pump_messages_event_); + } +} + +bool WebPluginDelegateImpl::PlatformInitialize() { + plugin_->SetWindow(windowed_handle_); + + if (windowless_ && !instance_->plugin_lib()->internal()) { + CreateDummyWindowForActivation(); + handle_event_pump_messages_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + plugin_->SetWindowlessPumpEvent(handle_event_pump_messages_event_); + } + + // We cannot patch internal plugins as they are not shared libraries. + if (!instance_->plugin_lib()->internal()) { + // Windowless plugins call the WindowFromPoint API and passes the result of + // that to the TrackPopupMenu API call as the owner window. This causes the + // API to fail as the API expects the window handle to live on the same + // thread as the caller. It works in the other browsers as the plugin lives + // on the browser thread. Our workaround is to intercept the TrackPopupMenu + // API and replace the window handle with the dummy activation window. + if (windowless_ && !g_iat_patch_track_popup_menu.Pointer()->is_patched()) { + g_iat_patch_track_popup_menu.Pointer()->Patch( + GetPluginPath().value().c_str(), "user32.dll", "TrackPopupMenu", + WebPluginDelegateImpl::TrackPopupMenuPatch); + } + + // Windowless plugins can set cursors by calling the SetCursor API. This + // works because the thread inputs of the browser UI thread and the plugin + // thread are attached. We intercept the SetCursor API for windowless + // plugins and remember the cursor being set. This is shipped over to the + // browser in the HandleEvent call, which ensures that the cursor does not + // change when a windowless plugin instance changes the cursor + // in a background tab. + if (windowless_ && !g_iat_patch_set_cursor.Pointer()->is_patched() && + (quirks_ & PLUGIN_QUIRK_PATCH_SETCURSOR)) { + g_iat_patch_set_cursor.Pointer()->Patch( + GetPluginPath().value().c_str(), "user32.dll", "SetCursor", + WebPluginDelegateImpl::SetCursorPatch); + } + + // The windowed flash plugin has a bug which occurs when the plugin enters + // fullscreen mode. It basically captures the mouse on WM_LBUTTONDOWN and + // does not release capture correctly causing it to stop receiving + // subsequent mouse events. This problem is also seen in Safari where there + // is code to handle this in the wndproc. However the plugin subclasses the + // window again in WM_LBUTTONDOWN before entering full screen. As a result + // Safari does not receive the WM_LBUTTONUP message. To workaround this + // issue we use a per thread mouse hook. This bug does not occur in Firefox + // and opera. Firefox has code similar to Safari. It could well be a bug in + // the flash plugin, which only occurs in webkit based browsers. + if (quirks_ & PLUGIN_QUIRK_HANDLE_MOUSE_CAPTURE) { + mouse_hook_ = SetWindowsHookEx(WH_MOUSE, MouseHookProc, NULL, + GetCurrentThreadId()); + } + } + + // On XP, WMP will use its old UI unless a registry key under HKLM has the + // name of the current process. We do it in the installer for admin users, + // for the rest patch this function. + if ((quirks_ & PLUGIN_QUIRK_PATCH_REGENUMKEYEXW) && + win_util::GetWinVersion() == win_util::WINVERSION_XP && + !RegKey().Open(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\MediaPlayer\\ShimInclusionList\\chrome.exe") && + !g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) { + g_iat_patch_reg_enum_key_ex_w.Pointer()->Patch( + L"wmpdxm.dll", "advapi32.dll", "RegEnumKeyExW", + WebPluginDelegateImpl::RegEnumKeyExWPatch); + } + + return true; +} + +void WebPluginDelegateImpl::PlatformDestroyInstance() { + if (!instance_->plugin_lib()) + return; + + // Unpatch if this is the last plugin instance. + if (instance_->plugin_lib()->instance_count() != 1) + return; + + if (g_iat_patch_set_cursor.Pointer()->is_patched()) + g_iat_patch_set_cursor.Pointer()->Unpatch(); + + if (g_iat_patch_track_popup_menu.Pointer()->is_patched()) + g_iat_patch_track_popup_menu.Pointer()->Unpatch(); + + if (g_iat_patch_reg_enum_key_ex_w.Pointer()->is_patched()) + g_iat_patch_reg_enum_key_ex_w.Pointer()->Unpatch(); + + if (mouse_hook_) { + UnhookWindowsHookEx(mouse_hook_); + mouse_hook_ = NULL; + } +} + +void WebPluginDelegateImpl::Paint(skia::PlatformCanvas* canvas, + const gfx::Rect& rect) { + if (windowless_) { + HDC hdc = canvas->beginPlatformPaint(); + WindowlessPaint(hdc, rect); + canvas->endPlatformPaint(); + } +} + +void WebPluginDelegateImpl::Print(HDC hdc) { + // Disabling the call to NPP_Print as it causes a crash in + // flash in some cases. In any case this does not work as expected + // as the EMF meta file dc passed in needs to be created with the + // the plugin window dc as its sibling dc and the window rect + // in .01 mm units. +#if 0 + NPPrint npprint; + npprint.mode = NP_EMBED; + npprint.print.embedPrint.platformPrint = reinterpret_cast<void*>(hdc); + npprint.print.embedPrint.window = window_; + instance()->NPP_Print(&npprint); +#endif +} + +void WebPluginDelegateImpl::InstallMissingPlugin() { + NPEvent evt; + evt.event = default_plugin::kInstallMissingPluginMessage; + evt.lParam = 0; + evt.wParam = 0; + instance()->NPP_HandleEvent(&evt); +} + +bool WebPluginDelegateImpl::WindowedCreatePlugin() { + DCHECK(!windowed_handle_); + + RegisterNativeWindowClass(); + + // The window will be sized and shown later. + windowed_handle_ = CreateWindowEx( + WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR, + kNativeWindowClassName, + 0, + WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, + 0, + 0, + 0, + parent_, + 0, + GetModuleHandle(NULL), + 0); + if (windowed_handle_ == 0) + return false; + + if (IsWindow(parent_)) { + // This is a tricky workaround for Issue 2673 in chromium "Flash: IME not + // available". To use IMEs in this window, we have to make Windows attach + // IMEs to this window (i.e. load IME DLLs, attach them to this process, + // and add their message hooks to this window). Windows attaches IMEs while + // this process creates a top-level window. On the other hand, to layout + // this window correctly in the given parent window (RenderWidgetHostHWND), + // this window should be a child window of the parent window. + // To satisfy both of the above conditions, this code once creates a + // top-level window and change it to a child window of the parent window. + SetWindowLongPtr(windowed_handle_, GWL_STYLE, + WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS); + SetParent(windowed_handle_, parent_); + } + + BOOL result = SetProp(windowed_handle_, kWebPluginDelegateProperty, this); + DCHECK(result == TRUE) << "SetProp failed, last error = " << GetLastError(); + // Get the name of the plugin, create an atom and set that in a window + // property. Use an atom so that other processes can access the name of + // the plugin that this window is hosting + if (instance_ != NULL) { + NPAPI::PluginLib* plugin_lib = instance()->plugin_lib(); + if (plugin_lib != NULL) { + std::wstring plugin_name = plugin_lib->plugin_info().name; + if (!plugin_name.empty()) { + ATOM plugin_name_atom = GlobalAddAtomW(plugin_name.c_str()); + DCHECK(0 != plugin_name_atom); + result = SetProp(windowed_handle_, + kPluginNameAtomProperty, + reinterpret_cast<HANDLE>(plugin_name_atom)); + DCHECK(result == TRUE) << "SetProp failed, last error = " << + GetLastError(); + } + } + } + + // Calling SetWindowLongPtrA here makes the window proc ASCII, which is + // required by at least the Shockwave Director plug-in. + SetWindowLongPtrA( + windowed_handle_, GWL_WNDPROC, reinterpret_cast<LONG>(DefWindowProcA)); + + return true; +} + +void WebPluginDelegateImpl::WindowedDestroyWindow() { + if (windowed_handle_ != NULL) { + // Unsubclass the window. + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); + if (current_wnd_proc == NativeWndProc) { + SetWindowLongPtr(windowed_handle_, + GWLP_WNDPROC, + reinterpret_cast<LONG>(plugin_wnd_proc_)); + } + + plugin_->WillDestroyWindow(windowed_handle_); + + DestroyWindow(windowed_handle_); + windowed_handle_ = 0; + } +} + +// Erase all messages in the queue destined for a particular window. +// When windows are closing, callers should use this function to clear +// the queue. +// static +void WebPluginDelegateImpl::ClearThrottleQueueForWindow(HWND window) { + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + + ThrottleQueue::iterator it; + for (it = throttle_queue->begin(); it != throttle_queue->end(); ) { + if (it->hwnd == window) { + it = throttle_queue->erase(it); + } else { + it++; + } + } +} + +// Delayed callback for processing throttled messages. +// Throttled messages are aggregated globally across all plugins. +// static +void WebPluginDelegateImpl::OnThrottleMessage() { + // The current algorithm walks the list and processes the first + // message it finds for each plugin. It is important to service + // all active plugins with each pass through the throttle, otherwise + // we see video jankiness. Copy the set to notify before notifying + // since we may re-enter OnThrottleMessage from CallWindowProc! + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + ThrottleQueue notify_queue; + std::set<HWND> processed; + + ThrottleQueue::iterator it = throttle_queue->begin(); + while (it != throttle_queue->end()) { + const MSG& msg = *it; + if (processed.find(msg.hwnd) == processed.end()) { + processed.insert(msg.hwnd); + notify_queue.push_back(msg); + it = throttle_queue->erase(it); + } else { + it++; + } + } + + for (it = notify_queue.begin(); it != notify_queue.end(); ++it) { + const MSG& msg = *it; + WNDPROC proc = reinterpret_cast<WNDPROC>(msg.time); + // It is possible that the window was closed after we queued + // this message. This is a rare event; just verify the window + // is alive. (see also bug 1259488) + if (IsWindow(msg.hwnd)) + CallWindowProc(proc, msg.hwnd, msg.message, msg.wParam, msg.lParam); + } + + if (!throttle_queue->empty()) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), + kFlashWMUSERMessageThrottleDelayMs); + } +} + +// Schedule a windows message for delivery later. +// static +void WebPluginDelegateImpl::ThrottleMessage(WNDPROC proc, HWND hwnd, + UINT message, WPARAM wParam, + LPARAM lParam) { + MSG msg; + msg.time = reinterpret_cast<DWORD>(proc); + msg.hwnd = hwnd; + msg.message = message; + msg.wParam = wParam; + msg.lParam = lParam; + + ThrottleQueue* throttle_queue = g_throttle_queue.Pointer(); + + throttle_queue->push_back(msg); + + if (throttle_queue->size() == 1) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + NewRunnableFunction(&WebPluginDelegateImpl::OnThrottleMessage), + kFlashWMUSERMessageThrottleDelayMs); + } +} + +// We go out of our way to find the hidden windows created by Flash for +// windowless plugins. We throttle the rate at which they deliver messages +// so that they will not consume outrageous amounts of CPU. +// static +LRESULT CALLBACK WebPluginDelegateImpl::FlashWindowlessWndProc(HWND hwnd, + UINT message, WPARAM wparam, LPARAM lparam) { + std::map<HWND, WNDPROC>::iterator index = + g_window_handle_proc_map.Get().find(hwnd); + + WNDPROC old_proc = (*index).second; + DCHECK(old_proc); + + switch (message) { + case WM_NCDESTROY: { + WebPluginDelegateImpl::ClearThrottleQueueForWindow(hwnd); + g_window_handle_proc_map.Get().erase(index); + break; + } + // Flash may flood the message queue with WM_USER+1 message causing 100% CPU + // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We + // prevent this by throttling the messages. + case WM_USER + 1: { + WebPluginDelegateImpl::ThrottleMessage(old_proc, hwnd, message, wparam, + lparam); + return TRUE; + } + default: { + break; + } + } + return CallWindowProc(old_proc, hwnd, message, wparam, lparam); +} + +// Callback for enumerating the Flash windows. +BOOL CALLBACK EnumFlashWindows(HWND window, LPARAM arg) { + WNDPROC wnd_proc = reinterpret_cast<WNDPROC>(arg); + TCHAR class_name[1024]; + if (!RealGetWindowClass(window, class_name, + sizeof(class_name)/sizeof(TCHAR))) { + LOG(ERROR) << "RealGetWindowClass failure: " << GetLastError(); + return FALSE; + } + + if (wcscmp(class_name, L"SWFlash_PlaceholderX")) + return TRUE; + + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(window, GWLP_WNDPROC)); + if (current_wnd_proc != wnd_proc) { + WNDPROC old_flash_proc = reinterpret_cast<WNDPROC>(SetWindowLongPtr( + window, GWLP_WNDPROC, + reinterpret_cast<LONG>(wnd_proc))); + DCHECK(old_flash_proc); + g_window_handle_proc_map.Get()[window] = old_flash_proc; + } + + return TRUE; +} + +bool WebPluginDelegateImpl::CreateDummyWindowForActivation() { + DCHECK(!dummy_window_for_activation_); + dummy_window_for_activation_ = CreateWindowEx( + 0, + L"Static", + kDummyActivationWindowName, + WS_CHILD, + 0, + 0, + 0, + 0, + parent_, + 0, + GetModuleHandle(NULL), + 0); + + if (dummy_window_for_activation_ == 0) + return false; + + // Flash creates background windows which use excessive CPU in our + // environment; we wrap these windows and throttle them so that they don't + // get out of hand. + if (!EnumThreadWindows(::GetCurrentThreadId(), EnumFlashWindows, + reinterpret_cast<LPARAM>( + &WebPluginDelegateImpl::FlashWindowlessWndProc))) { + // Log that this happened. Flash will still work; it just means the + // throttle isn't installed (and Flash will use more CPU). + NOTREACHED(); + LOG(ERROR) << "Failed to wrap all windowless Flash windows"; + } + return true; +} + +bool WebPluginDelegateImpl::WindowedReposition( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + if (!windowed_handle_) { + NOTREACHED(); + return false; + } + + if (window_rect_ == window_rect && clip_rect_ == clip_rect) + return false; + + // We only set the plugin's size here. Its position is moved elsewhere, which + // allows the window moves/scrolling/clipping to be synchronized with the page + // and other windows. + // If the plugin window has no parent, then don't focus it because it isn't + // being displayed anywhere. See: + // http://code.google.com/p/chromium/issues/detail?id=32658 + if (window_rect.size() != window_rect_.size()) { + UINT flags = SWP_NOMOVE | SWP_NOZORDER; + if (!GetParent(windowed_handle_)) + flags |= SWP_NOACTIVATE; + ::SetWindowPos(windowed_handle_, + NULL, + 0, + 0, + window_rect.width(), + window_rect.height(), + flags); + } + + window_rect_ = window_rect; + clip_rect_ = clip_rect; + + // Ensure that the entire window gets repainted. + ::InvalidateRect(windowed_handle_, NULL, FALSE); + + return true; +} + +void WebPluginDelegateImpl::WindowedSetWindow() { + if (!instance_) + return; + + if (!windowed_handle_) { + NOTREACHED(); + return; + } + + instance()->set_window_handle(windowed_handle_); + + DCHECK(!instance()->windowless()); + + window_.clipRect.top = std::max(0, clip_rect_.y()); + window_.clipRect.left = std::max(0, clip_rect_.x()); + window_.clipRect.bottom = std::max(0, clip_rect_.y() + clip_rect_.height()); + window_.clipRect.right = std::max(0, clip_rect_.x() + clip_rect_.width()); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = 0; + window_.y = 0; + + window_.window = windowed_handle_; + window_.type = NPWindowTypeWindow; + + // Reset this flag before entering the instance in case of side-effects. + windowed_did_set_window_ = true; + + NPError err = instance()->NPP_SetWindow(&window_); + if (quirks_ & PLUGIN_QUIRK_SETWINDOW_TWICE) + instance()->NPP_SetWindow(&window_); + + WNDPROC current_wnd_proc = reinterpret_cast<WNDPROC>( + GetWindowLongPtr(windowed_handle_, GWLP_WNDPROC)); + if (current_wnd_proc != NativeWndProc) { + plugin_wnd_proc_ = reinterpret_cast<WNDPROC>(SetWindowLongPtr( + windowed_handle_, GWLP_WNDPROC, reinterpret_cast<LONG>(NativeWndProc))); + } +} + +ATOM WebPluginDelegateImpl::RegisterNativeWindowClass() { + static bool have_registered_window_class = false; + if (have_registered_window_class == true) + return true; + + have_registered_window_class = true; + + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = DummyWindowProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = GetModuleHandle(NULL); + wcex.hIcon = 0; + wcex.hCursor = 0; + // Some plugins like windows media player 11 create child windows parented + // by our plugin window, where the media content is rendered. These plugins + // dont implement WM_ERASEBKGND, which causes painting issues, when the + // window where the media is rendered is moved around. DefWindowProc does + // implement WM_ERASEBKGND correctly if we have a valid background brush. + wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW+1); + wcex.lpszMenuName = 0; + wcex.lpszClassName = kNativeWindowClassName; + wcex.hIconSm = 0; + + return RegisterClassEx(&wcex); +} + +LRESULT CALLBACK WebPluginDelegateImpl::DummyWindowProc( + HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + // This is another workaround for Issue 2673 in chromium "Flash: IME not + // available". Somehow, the CallWindowProc() function does not dispatch + // window messages when its first parameter is a handle representing the + // DefWindowProc() function. To avoid this problem, this code creates a + // wrapper function which just encapsulates the DefWindowProc() function + // and set it as the window procedure of a windowed plug-in. + return DefWindowProc(hWnd, message, wParam, lParam); +} + +// Returns true if the message passed in corresponds to a user gesture. +static bool IsUserGestureMessage(unsigned int message) { + switch (message) { + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_KEYUP: + return true; + + default: + break; + } + + return false; +} + +LRESULT CALLBACK WebPluginDelegateImpl::NativeWndProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + WebPluginDelegateImpl* delegate = reinterpret_cast<WebPluginDelegateImpl*>( + GetProp(hwnd, kWebPluginDelegateProperty)); + if (!delegate) { + NOTREACHED(); + return 0; + } + + if (message == delegate->last_message_ && + delegate->GetQuirks() & PLUGIN_QUIRK_DONT_CALL_WND_PROC_RECURSIVELY && + delegate->is_calling_wndproc) { + // Real may go into a state where it recursively dispatches the same event + // when subclassed. See https://bugzilla.mozilla.org/show_bug.cgi?id=192914 + // We only do the recursive check for Real because it's possible and valid + // for a plugin to synchronously dispatch a message to itself such that it + // looks like it's in recursion. + return TRUE; + } + + // Flash may flood the message queue with WM_USER+1 message causing 100% CPU + // usage. See https://bugzilla.mozilla.org/show_bug.cgi?id=132759. We + // prevent this by throttling the messages. + if (message == WM_USER + 1 && + delegate->GetQuirks() & PLUGIN_QUIRK_THROTTLE_WM_USER_PLUS_ONE) { + WebPluginDelegateImpl::ThrottleMessage(delegate->plugin_wnd_proc_, hwnd, + message, wparam, lparam); + return FALSE; + } + + LRESULT result; + uint32 old_message = delegate->last_message_; + delegate->last_message_ = message; + + static UINT custom_msg = RegisterWindowMessage(kPaintMessageName); + if (message == custom_msg) { + // Get the invalid rect which is in screen coordinates and convert to + // window coordinates. + gfx::Rect invalid_rect; + invalid_rect.set_x(wparam >> 16); + invalid_rect.set_y(wparam & 0xFFFF); + invalid_rect.set_width(lparam >> 16); + invalid_rect.set_height(lparam & 0xFFFF); + + RECT window_rect; + GetWindowRect(hwnd, &window_rect); + invalid_rect.Offset(-window_rect.left, -window_rect.top); + + // The plugin window might have non-client area. If we don't pass in + // RDW_FRAME then the children don't receive WM_NCPAINT messages while + // scrolling, which causes painting problems (http://b/issue?id=923945). + uint32 flags = RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_FRAME; + + // If a plugin (like Google Earth or Java) has child windows that are hosted + // in a different process, then RedrawWindow with UPDATENOW will + // synchronously wait for this call to complete. Some messages are pumped + // but not others, which could lead to a deadlock. So avoid reentrancy by + // only synchronously calling RedrawWindow once at a time. + if (old_message != custom_msg) + flags |= RDW_UPDATENOW; + + RedrawWindow(hwnd, &invalid_rect.ToRECT(), NULL, flags); + result = FALSE; + } else { + delegate->is_calling_wndproc = true; + + if (!delegate->user_gesture_message_posted_ && + IsUserGestureMessage(message)) { + delegate->user_gesture_message_posted_ = true; + + delegate->instance()->PushPopupsEnabledState(true); + + MessageLoop::current()->PostDelayedTask(FROM_HERE, + delegate->user_gesture_msg_factory_.NewRunnableMethod( + &WebPluginDelegateImpl::OnUserGestureEnd), + kWindowedPluginPopupTimerMs); + } + + HandleCaptureForMessage(hwnd, message); + + // Maintain a local/global stack for the g_current_plugin_instance variable + // as this may be a nested invocation. + WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; + + g_current_plugin_instance = delegate; + + result = CallWindowProc( + delegate->plugin_wnd_proc_, hwnd, message, wparam, lparam); + + delegate->is_calling_wndproc = false; + g_current_plugin_instance = last_plugin_instance; + + if (message == WM_NCDESTROY) { + RemoveProp(hwnd, kWebPluginDelegateProperty); + ATOM plugin_name_atom = reinterpret_cast<ATOM>( + RemoveProp(hwnd, kPluginNameAtomProperty)); + if (plugin_name_atom != 0) + GlobalDeleteAtom(plugin_name_atom); + ClearThrottleQueueForWindow(hwnd); + } + } + delegate->last_message_ = old_message; + return result; +} + +void WebPluginDelegateImpl::WindowlessUpdateGeometry( + const gfx::Rect& window_rect, + const gfx::Rect& clip_rect) { + bool window_rect_changed = (window_rect_ != window_rect); + // Only resend to the instance if the geometry has changed. + if (!window_rect_changed && clip_rect == clip_rect_) + return; + + clip_rect_ = clip_rect; + window_rect_ = window_rect; + + WindowlessSetWindow(); + + if (window_rect_changed) { + WINDOWPOS win_pos = {0}; + win_pos.x = window_rect_.x(); + win_pos.y = window_rect_.y(); + win_pos.cx = window_rect_.width(); + win_pos.cy = window_rect_.height(); + + NPEvent pos_changed_event; + pos_changed_event.event = WM_WINDOWPOSCHANGED; + pos_changed_event.wParam = 0; + pos_changed_event.lParam = PtrToUlong(&win_pos); + + instance()->NPP_HandleEvent(&pos_changed_event); + } +} + +void WebPluginDelegateImpl::WindowlessPaint(HDC hdc, + const gfx::Rect& damage_rect) { + DCHECK(hdc); + + RECT damage_rect_win; + damage_rect_win.left = damage_rect.x(); // + window_rect_.x(); + damage_rect_win.top = damage_rect.y(); // + window_rect_.y(); + damage_rect_win.right = damage_rect_win.left + damage_rect.width(); + damage_rect_win.bottom = damage_rect_win.top + damage_rect.height(); + + window_.window = hdc; + + NPEvent paint_event; + paint_event.event = WM_PAINT; + // NOTE: NPAPI is not 64bit safe. It puts pointers into 32bit values. + paint_event.wParam = PtrToUlong(hdc); + paint_event.lParam = PtrToUlong(&damage_rect_win); + static StatsRate plugin_paint("Plugin.Paint"); + StatsScope<StatsRate> scope(plugin_paint); + instance()->NPP_HandleEvent(&paint_event); +} + +void WebPluginDelegateImpl::WindowlessSetWindow() { + if (!instance()) + return; + + if (window_rect_.IsEmpty()) // wait for geometry to be set. + return; + + DCHECK(instance()->windowless()); + + window_.clipRect.top = clip_rect_.y(); + window_.clipRect.left = clip_rect_.x(); + window_.clipRect.bottom = clip_rect_.y() + clip_rect_.height(); + window_.clipRect.right = clip_rect_.x() + clip_rect_.width(); + window_.height = window_rect_.height(); + window_.width = window_rect_.width(); + window_.x = window_rect_.x(); + window_.y = window_rect_.y(); + window_.type = NPWindowTypeDrawable; + DrawableContextEnforcer enforcer(&window_); + + NPError err = instance()->NPP_SetWindow(&window_); + DCHECK(err == NPERR_NO_ERROR); +} + +void WebPluginDelegateImpl::SetFocus(bool focused) { + DCHECK(instance()->windowless()); + + NPEvent focus_event; + focus_event.event = focused ? WM_SETFOCUS : WM_KILLFOCUS; + focus_event.wParam = 0; + focus_event.lParam = 0; + + instance()->NPP_HandleEvent(&focus_event); +} + +static bool NPEventFromWebMouseEvent(const WebMouseEvent& event, + NPEvent *np_event) { + np_event->lParam = static_cast<uint32>(MAKELPARAM(event.windowX, + event.windowY)); + np_event->wParam = 0; + + if (event.modifiers & WebInputEvent::ControlKey) + np_event->wParam |= MK_CONTROL; + if (event.modifiers & WebInputEvent::ShiftKey) + np_event->wParam |= MK_SHIFT; + if (event.modifiers & WebInputEvent::LeftButtonDown) + np_event->wParam |= MK_LBUTTON; + if (event.modifiers & WebInputEvent::MiddleButtonDown) + np_event->wParam |= MK_MBUTTON; + if (event.modifiers & WebInputEvent::RightButtonDown) + np_event->wParam |= MK_RBUTTON; + + switch (event.type) { + case WebInputEvent::MouseMove: + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + np_event->event = WM_MOUSEMOVE; + return true; + case WebInputEvent::MouseDown: + switch (event.button) { + case WebMouseEvent::ButtonLeft: + np_event->event = WM_LBUTTONDOWN; + break; + case WebMouseEvent::ButtonMiddle: + np_event->event = WM_MBUTTONDOWN; + break; + case WebMouseEvent::ButtonRight: + np_event->event = WM_RBUTTONDOWN; + break; + } + return true; + case WebInputEvent::MouseUp: + switch (event.button) { + case WebMouseEvent::ButtonLeft: + np_event->event = WM_LBUTTONUP; + break; + case WebMouseEvent::ButtonMiddle: + np_event->event = WM_MBUTTONUP; + break; + case WebMouseEvent::ButtonRight: + np_event->event = WM_RBUTTONUP; + break; + } + return true; + default: + NOTREACHED(); + return false; + } +} + +static bool NPEventFromWebKeyboardEvent(const WebKeyboardEvent& event, + NPEvent *np_event) { + np_event->wParam = event.windowsKeyCode; + + switch (event.type) { + case WebInputEvent::KeyDown: + np_event->event = WM_KEYDOWN; + np_event->lParam = 0; + return true; + case WebInputEvent::Char: + np_event->event = WM_CHAR; + np_event->lParam = 0; + return true; + case WebInputEvent::KeyUp: + np_event->event = WM_KEYUP; + np_event->lParam = 0x8000; + return true; + default: + NOTREACHED(); + return false; + } +} + +static bool NPEventFromWebInputEvent(const WebInputEvent& event, + NPEvent* np_event) { + switch (event.type) { + case WebInputEvent::MouseMove: + case WebInputEvent::MouseLeave: + case WebInputEvent::MouseEnter: + case WebInputEvent::MouseDown: + case WebInputEvent::MouseUp: + if (event.size < sizeof(WebMouseEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebMouseEvent( + *static_cast<const WebMouseEvent*>(&event), np_event); + case WebInputEvent::KeyDown: + case WebInputEvent::Char: + case WebInputEvent::KeyUp: + if (event.size < sizeof(WebKeyboardEvent)) { + NOTREACHED(); + return false; + } + return NPEventFromWebKeyboardEvent( + *static_cast<const WebKeyboardEvent*>(&event), np_event); + default: + return false; + } +} + +bool WebPluginDelegateImpl::PlatformHandleInputEvent( + const WebInputEvent& event, WebCursorInfo* cursor_info) { + DCHECK(cursor_info != NULL); + + NPEvent np_event; + if (!NPEventFromWebInputEvent(event, &np_event)) { + return false; + } + + // Synchronize the keyboard layout with the one of the browser process. Flash + // uses the keyboard layout of this window to verify a WM_CHAR message is + // valid. That is, Flash discards a WM_CHAR message unless its character is + // the one translated with ToUnicode(). (Since a plug-in is running on a + // separate process from the browser process, we need to syncronize it + // manually.) + if (np_event.event == WM_CHAR) { + if (!keyboard_layout_) + keyboard_layout_ = GetKeyboardLayout(GetCurrentThreadId()); + if (!parent_thread_id_) + parent_thread_id_ = GetWindowThreadProcessId(parent_, NULL); + HKL parent_layout = GetKeyboardLayout(parent_thread_id_); + if (keyboard_layout_ != parent_layout) { + std::wstring layout_name(StringPrintf(L"%08x", parent_layout)); + LoadKeyboardLayout(layout_name.c_str(), KLF_ACTIVATE); + keyboard_layout_ = parent_layout; + } + } + + if (ShouldTrackEventForModalLoops(&np_event)) { + // A windowless plugin can enter a modal loop in a NPP_HandleEvent call. + // For e.g. Flash puts up a context menu when we right click on the + // windowless plugin area. We detect this by setting up a message filter + // hook pror to calling NPP_HandleEvent on the plugin and unhook on + // return from NPP_HandleEvent. If the plugin does enter a modal loop + // in that context we unhook on receiving the first notification in + // the message filter hook. + handle_event_message_filter_hook_ = + SetWindowsHookEx(WH_MSGFILTER, HandleEventMessageFilterHook, NULL, + GetCurrentThreadId()); + } + + bool old_task_reentrancy_state = + MessageLoop::current()->NestableTasksAllowed(); + + + // Maintain a local/global stack for the g_current_plugin_instance variable + // as this may be a nested invocation. + WebPluginDelegateImpl* last_plugin_instance = g_current_plugin_instance; + + g_current_plugin_instance = this; + + handle_event_depth_++; + + bool ret = instance()->NPP_HandleEvent(&np_event) != 0; + + // Flash and SilverLight always return false, even when they swallow the + // event. Flash does this because it passes the event to its window proc, + // which is supposed to return 0 if an event was handled. There are few + // exceptions, such as IME, where it sometimes returns true. + ret = true; + + if (np_event.event == WM_MOUSEMOVE) { + // Snag a reference to the current cursor ASAP in case the plugin modified + // it. There is a nasty race condition here with the multiprocess browser + // as someone might be setting the cursor in the main process as well. + current_windowless_cursor_.GetCursorInfo(cursor_info); + } + + handle_event_depth_--; + + g_current_plugin_instance = last_plugin_instance; + + MessageLoop::current()->SetNestableTasksAllowed(old_task_reentrancy_state); + + // We could have multiple NPP_HandleEvent calls nested together in case + // the plugin enters a modal loop. Reset the pump messages event when + // the outermost NPP_HandleEvent call unwinds. + if (handle_event_depth_ == 0) { + ResetEvent(handle_event_pump_messages_event_); + } + + return ret; +} + + +void WebPluginDelegateImpl::OnModalLoopEntered() { + DCHECK(handle_event_pump_messages_event_ != NULL); + SetEvent(handle_event_pump_messages_event_); + + MessageLoop::current()->SetNestableTasksAllowed(true); + + UnhookWindowsHookEx(handle_event_message_filter_hook_); + handle_event_message_filter_hook_ = NULL; +} + +bool WebPluginDelegateImpl::ShouldTrackEventForModalLoops(NPEvent* event) { + if (event->event == WM_RBUTTONDOWN) + return true; + return false; +} + +void WebPluginDelegateImpl::OnUserGestureEnd() { + user_gesture_message_posted_ = false; + instance()->PopPopupsEnabledState(); +} + +BOOL WINAPI WebPluginDelegateImpl::TrackPopupMenuPatch( + HMENU menu, unsigned int flags, int x, int y, int reserved, + HWND window, const RECT* rect) { + + HWND last_focus_window = NULL; + + if (g_current_plugin_instance) { + unsigned long window_process_id = 0; + unsigned long window_thread_id = + GetWindowThreadProcessId(window, &window_process_id); + // TrackPopupMenu fails if the window passed in belongs to a different + // thread. + if (::GetCurrentThreadId() != window_thread_id) { + window = g_current_plugin_instance->dummy_window_for_activation_; + } + + // To ensure that the plugin receives keyboard events we set focus to the + // dummy window. + // TODO(iyengar) We need a framework in the renderer to identify which + // windowless plugin is under the mouse and to handle this. This would + // also require some changes in RenderWidgetHost to detect this in the + // WM_MOUSEACTIVATE handler and inform the renderer accordingly. + if (g_current_plugin_instance->dummy_window_for_activation_) { + last_focus_window = + ::SetFocus(g_current_plugin_instance->dummy_window_for_activation_); + } + } + + BOOL result = TrackPopupMenu(menu, flags, x, y, reserved, window, rect); + + if (IsWindow(last_focus_window)) { + // The Flash plugin at times sets focus to its hidden top level window + // with class name SWFlash_PlaceholderX. This causes the chrome browser + // window to receive a WM_ACTIVATEAPP message as a top level window from + // another thread is now active. We end up in a state where the chrome + // browser window is not active even though the user clicked on it. + // Our workaround for this is to send over a raw + // WM_LBUTTONDOWN/WM_LBUTTONUP combination to the last focus window, which + // does the trick. + if (g_current_plugin_instance->dummy_window_for_activation_ != + ::GetFocus()) { + INPUT input_info = {0}; + input_info.type = INPUT_MOUSE; + input_info.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + ::SendInput(1, &input_info, sizeof(INPUT)); + + input_info.type = INPUT_MOUSE; + input_info.mi.dwFlags = MOUSEEVENTF_LEFTUP; + ::SendInput(1, &input_info, sizeof(INPUT)); + } else { + ::SetFocus(last_focus_window); + } + } + + return result; +} + +HCURSOR WINAPI WebPluginDelegateImpl::SetCursorPatch(HCURSOR cursor) { + // The windowless flash plugin periodically calls SetCursor in a wndproc + // instantiated on the plugin thread. This causes annoying cursor flicker + // when the mouse is moved on a foreground tab, with a windowless plugin + // instance in a background tab. We just ignore the call here. + if (!g_current_plugin_instance) { + HCURSOR current_cursor = GetCursor(); + if (current_cursor != cursor) { + ::SetCursor(cursor); + } + return current_cursor; + } + + if (!g_current_plugin_instance->IsWindowless()) { + return ::SetCursor(cursor); + } + + // It is ok to pass NULL here to GetCursor as we are not looking for cursor + // types defined by Webkit. + HCURSOR previous_cursor = + g_current_plugin_instance->current_windowless_cursor_.GetCursor(NULL); + + g_current_plugin_instance->current_windowless_cursor_.InitFromExternalCursor( + cursor); + return previous_cursor; +} + +LONG WINAPI WebPluginDelegateImpl::RegEnumKeyExWPatch( + HKEY key, DWORD index, LPWSTR name, LPDWORD name_size, LPDWORD reserved, + LPWSTR class_name, LPDWORD class_size, PFILETIME last_write_time) { + DWORD orig_size = *name_size; + LONG rv = RegEnumKeyExW(key, index, name, name_size, reserved, class_name, + class_size, last_write_time); + if (rv == ERROR_SUCCESS && + GetKeyPath(key).find(L"Microsoft\\MediaPlayer\\ShimInclusionList") != + std::wstring::npos) { + static const wchar_t kChromeExeName[] = L"chrome.exe"; + wcsncpy_s(name, orig_size, kChromeExeName, arraysize(kChromeExeName)); + *name_size = + std::min(orig_size, static_cast<DWORD>(arraysize(kChromeExeName))); + } + + return rv; +} + +void WebPluginDelegateImpl::HandleCaptureForMessage(HWND window, + UINT message) { + if (!WebPluginDelegateImpl::IsPluginDelegateWindow(window)) + return; + + switch (message) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + ::SetCapture(window); + break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + ::ReleaseCapture(); + break; + + default: + break; + } +} diff --git a/webkit/glue/plugins/webplugin_file_delegate.h b/webkit/glue/plugins/webplugin_file_delegate.h new file mode 100644 index 0000000..162516c --- /dev/null +++ b/webkit/glue/plugins/webplugin_file_delegate.h @@ -0,0 +1,35 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace webkit_glue { + +// Interface for the NPAPI file extensions. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginFileDelegate { + public: + // See NPChooseFilePtr in npapi_extensions.h. Returns true on success, on + // cancel, returns true but *filename will be filled with an empty FilePath + // and *handle will be 0. + virtual bool ChooseFile(const char* mime_types, + int mode, + NPChooseFileCallback callback, + void* user_data) { + return false; + } + + protected: + WebPluginFileDelegate() {} + virtual ~WebPluginFileDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_FILE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_impl.cc b/webkit/glue/plugins/webplugin_impl.cc new file mode 100644 index 0000000..1660ede --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl.cc @@ -0,0 +1,1305 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/webplugin_impl.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "gfx/rect.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebConsoleMessage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCookieJar.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDevToolsAgent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPBody.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPHeaderVisitor.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginParams.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/multipart_response_delegate.h" +#include "webkit/glue/plugins/plugin_host.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugin_delegate.h" +#include "webkit/glue/plugins/webplugin_page_delegate.h" + +using WebKit::WebCanvas; +using WebKit::WebConsoleMessage; +using WebKit::WebCookieJar; +using WebKit::WebCString; +using WebKit::WebCursorInfo; +using WebKit::WebData; +using WebKit::WebDataSource; +using WebKit::WebDevToolsAgent; +using WebKit::WebFrame; +using WebKit::WebHTTPBody; +using WebKit::WebHTTPHeaderVisitor; +using WebKit::WebInputEvent; +using WebKit::WebKeyboardEvent; +using WebKit::WebMouseEvent; +using WebKit::WebPluginContainer; +using WebKit::WebPluginParams; +using WebKit::WebRect; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; +using WebKit::WebVector; +using WebKit::WebView; +using webkit_glue::MultipartResponseDelegate; + +namespace webkit_glue { +namespace { + +// This class handles individual multipart responses. It is instantiated when +// we receive HTTP status code 206 in the HTTP response. This indicates +// that the response could have multiple parts each separated by a boundary +// specified in the response header. +class MultiPartResponseClient : public WebURLLoaderClient { + public: + explicit MultiPartResponseClient(WebPluginResourceClient* resource_client) + : resource_client_(resource_client) { + Clear(); + } + + virtual void willSendRequest( + WebURLLoader*, WebURLRequest&, const WebURLResponse&) {} + virtual void didSendData( + WebURLLoader*, unsigned long long, unsigned long long) {} + + // Called when the multipart parser encounters an embedded multipart + // response. + virtual void didReceiveResponse( + WebURLLoader*, const WebURLResponse& response) { + if (!MultipartResponseDelegate::ReadContentRanges( + response, + &byte_range_lower_bound_, + &byte_range_upper_bound_)) { + NOTREACHED(); + return; + } + + resource_response_ = response; + } + + // Receives individual part data from a multipart response. + virtual void didReceiveData( + WebURLLoader*, const char* data, int data_size) { + // TODO(ananta) + // We should defer further loads on multipart resources on the same lines + // as regular resources requested by plugins to prevent reentrancy. + resource_client_->DidReceiveData( + data, data_size, byte_range_lower_bound_); + byte_range_lower_bound_ += data_size; + } + + virtual void didFinishLoading(WebURLLoader*) {} + virtual void didFail(WebURLLoader*, const WebURLError&) {} + + void Clear() { + resource_response_.reset(); + byte_range_lower_bound_ = 0; + byte_range_upper_bound_ = 0; + } + + private: + WebURLResponse resource_response_; + // The lower bound of the byte range. + int byte_range_lower_bound_; + // The upper bound of the byte range. + int byte_range_upper_bound_; + // The handler for the data. + WebPluginResourceClient* resource_client_; +}; + +class HeaderFlattener : public WebHTTPHeaderVisitor { + public: + HeaderFlattener(std::string* buf) : buf_(buf) { + } + + virtual void visitHeader(const WebString& name, const WebString& value) { + // TODO(darin): Should we really exclude headers with an empty value? + if (!name.isEmpty() && !value.isEmpty()) { + buf_->append(name.utf8()); + buf_->append(": "); + buf_->append(value.utf8()); + buf_->append("\n"); + } + } + + private: + std::string* buf_; +}; + +std::string GetAllHeaders(const WebURLResponse& response) { + // TODO(darin): It is possible for httpStatusText to be empty and still have + // an interesting response, so this check seems wrong. + std::string result; + const WebString& status = response.httpStatusText(); + if (status.isEmpty()) + return result; + + // TODO(darin): Shouldn't we also report HTTP version numbers? + result = StringPrintf("HTTP %d ", response.httpStatusCode()); + result.append(status.utf8()); + result.append("\n"); + + HeaderFlattener flattener(&result); + response.visitHTTPHeaderFields(&flattener); + + return result; +} + +struct ResponseInfo { + GURL url; + std::string mime_type; + uint32 last_modified; + uint32 expected_length; +}; + +void GetResponseInfo(const WebURLResponse& response, + ResponseInfo* response_info) { + response_info->url = response.url(); + response_info->mime_type = response.mimeType().utf8(); + + // Measured in seconds since 12:00 midnight GMT, January 1, 1970. + response_info->last_modified = + static_cast<uint32>(response.lastModifiedDate()); + + // If the length comes in as -1, then it indicates that it was not + // read off the HTTP headers. We replicate Safari webkit behavior here, + // which is to set it to 0. + response_info->expected_length = + static_cast<uint32>(std::max(response.expectedContentLength(), 0LL)); + + WebString content_encoding = + response.httpHeaderField(WebString::fromUTF8("Content-Encoding")); + if (!content_encoding.isNull() && + !EqualsASCII(content_encoding, "identity")) { + // Don't send the compressed content length to the plugin, which only + // cares about the decoded length. + response_info->expected_length = 0; + } +} + +} // namespace + +// WebKit::WebPlugin ---------------------------------------------------------- + +bool WebPluginImpl::initialize(WebPluginContainer* container) { + if (!page_delegate_) + return false; + + WebPluginDelegate* plugin_delegate = page_delegate_->CreatePluginDelegate( + file_path_, mime_type_); + if (!plugin_delegate) + return false; + + // Set the container before Initialize because the plugin may + // synchronously call NPN_GetValue to get its container during its + // initialization. + SetContainer(container); + bool ok = plugin_delegate->Initialize( + plugin_url_, arg_names_, arg_values_, this, load_manually_); + if (!ok) { + plugin_delegate->PluginDestroyed(); + return false; + } + + delegate_ = plugin_delegate; + + return true; +} + +void WebPluginImpl::destroy() { + SetContainer(NULL); + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +NPObject* WebPluginImpl::scriptableObject() { + return delegate_->GetPluginScriptableObject(); +} + +void WebPluginImpl::paint(WebCanvas* canvas, const WebRect& paint_rect) { + if (!delegate_ || !container_) + return; + +#if defined(OS_WIN) + // Force a geometry update if needed to allow plugins like media player + // which defer the initial geometry update to work. + container_->reportGeometry(); +#endif // OS_WIN + + // Note that |canvas| is only used when in windowless mode. + delegate_->Paint(canvas, paint_rect); +} + +void WebPluginImpl::updateGeometry( + const WebRect& window_rect, const WebRect& clip_rect, + const WebVector<WebRect>& cutout_rects, bool is_visible) { + WebPluginGeometry new_geometry; + new_geometry.window = window_; + new_geometry.window_rect = window_rect; + new_geometry.clip_rect = clip_rect; + new_geometry.visible = is_visible; + new_geometry.rects_valid = true; + for (size_t i = 0; i < cutout_rects.size(); ++i) + new_geometry.cutout_rects.push_back(cutout_rects[i]); + + // Only send DidMovePlugin if the geometry changed in some way. + if (window_ && + page_delegate_ && + (first_geometry_update_ || !new_geometry.Equals(geometry_))) { + page_delegate_->DidMovePlugin(new_geometry); + } + + // Only UpdateGeometry if either the window or clip rects have changed. + if (first_geometry_update_ || + new_geometry.window_rect != geometry_.window_rect || + new_geometry.clip_rect != geometry_.clip_rect) { + // Notify the plugin that its parameters have changed. + delegate_->UpdateGeometry(new_geometry.window_rect, new_geometry.clip_rect); + } + + // Initiate a download on the plugin url. This should be done for the + // first update geometry sequence. We need to ensure that the plugin + // receives the geometry update before it starts receiving data. + if (first_geometry_update_) { + // An empty url corresponds to an EMBED tag with no src attribute. + if (!load_manually_ && plugin_url_.is_valid()) { + // The Flash plugin hangs for a while if it receives data before + // receiving valid plugin geometry. By valid geometry we mean the + // geometry received by a call to setFrameRect in the Webkit + // layout code path. To workaround this issue we download the + // plugin source url on a timer. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, method_factory_.NewRunnableMethod( + &WebPluginImpl::OnDownloadPluginSrcUrl), 0); + } + } + +#if defined(OS_WIN) + // Don't cache the geometry during the first geometry update. The first + // geometry update sequence is received when Widget::setParent is called. + // For plugins like media player which have a bug where they only honor + // the first geometry update, we have a quirk which ignores the first + // geometry update. To ensure that these plugins work correctly in cases + // where we receive only one geometry update from webkit, we also force + // a geometry update during paint which should go out correctly as the + // initial geometry update was not cached. + if (!first_geometry_update_) + geometry_ = new_geometry; +#else // OS_WIN + geometry_ = new_geometry; +#endif // OS_WIN + first_geometry_update_ = false; +} + +void WebPluginImpl::updateFocus(bool focused) { + if (accepts_input_events_) + delegate_->SetFocus(focused); +} + +void WebPluginImpl::updateVisibility(bool visible) { + if (!window_ || !page_delegate_) + return; + + WebPluginGeometry move; + move.window = window_; + move.window_rect = gfx::Rect(); + move.clip_rect = gfx::Rect(); + move.rects_valid = false; + move.visible = visible; + + page_delegate_->DidMovePlugin(move); +} + +bool WebPluginImpl::acceptsInputEvents() { + return accepts_input_events_; +} + +bool WebPluginImpl::handleInputEvent( + const WebInputEvent& event, WebCursorInfo& cursor_info) { + return delegate_->HandleInputEvent(event, &cursor_info); +} + +void WebPluginImpl::didReceiveResponse(const WebURLResponse& response) { + ignore_response_error_ = false; + + ResponseInfo response_info; + GetResponseInfo(response, &response_info); + + delegate_->DidReceiveManualResponse( + response_info.url, + response_info.mime_type, + GetAllHeaders(response), + response_info.expected_length, + response_info.last_modified); +} + +void WebPluginImpl::didReceiveData(const char* data, int data_length) { + delegate_->DidReceiveManualData(data, data_length); +} + +void WebPluginImpl::didFinishLoading() { + delegate_->DidFinishManualLoading(); +} + +void WebPluginImpl::didFailLoading(const WebURLError& error) { + if (!ignore_response_error_) + delegate_->DidManualLoadFail(); +} + +void WebPluginImpl::didFinishLoadingFrameRequest( + const WebURL& url, void* notify_data) { + if (delegate_) { + // We're converting a void* into an arbitrary int id. Though + // these types are the same size on all the platforms we support, + // the compiler may complain as though they are different, so to + // make the casting gods happy go through an intptr_t (the union + // of void* and int) rather than converting straight across. + delegate_->DidFinishLoadWithReason( + url, NPRES_DONE, reinterpret_cast<intptr_t>(notify_data)); + } +} + +void WebPluginImpl::didFailLoadingFrameRequest( + const WebURL& url, void* notify_data, const WebURLError& error) { + if (!delegate_) + return; + + NPReason reason = + error.reason == net::ERR_ABORTED ? NPRES_USER_BREAK : NPRES_NETWORK_ERR; + // See comment in didFinishLoadingFrameRequest about the cast here. + delegate_->DidFinishLoadWithReason( + url, reason, reinterpret_cast<intptr_t>(notify_data)); +} + +bool WebPluginImpl::supportsPaginatedPrint() { + if (!delegate_) + return false; + return delegate_->PrintSupportsPrintExtension(); +} + +int WebPluginImpl::printBegin(const WebRect& printable_area, int printer_dpi) { + if (!delegate_) + return 0; + + if (!supportsPaginatedPrint()) + return 0; + + return delegate_->PrintBegin(printable_area, printer_dpi); +} + +bool WebPluginImpl::printPage(int page_number, WebCanvas* canvas) { + if (!delegate_) + return false; + + return delegate_->PrintPage(page_number, canvas); +} + +void WebPluginImpl::printEnd() { + if (delegate_) + delegate_->PrintEnd(); +} + +bool WebPluginImpl::hasSelection() const { + if (!delegate_) + return false; + + return delegate_->HasSelection(); +} + +WebKit::WebString WebPluginImpl::selectionAsText() const { + if (!delegate_) + return WebString(); + + return delegate_->GetSelectionAsText(); +} + +WebKit::WebString WebPluginImpl::selectionAsMarkup() const { + if (!delegate_) + return WebString(); + + return delegate_->GetSelectionAsMarkup(); +} + +void WebPluginImpl::setZoomFactor(float scale, bool text_only) { + if (delegate_) + delegate_->SetZoomFactor(scale, text_only); +} + +bool WebPluginImpl::startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier) { + if (!delegate_) + return false; + return delegate_->StartFind(search_text, case_sensitive, identifier); +} + +void WebPluginImpl::selectFindResult(bool forward) { + if (delegate_) + delegate_->SelectFindResult(forward); +} + +void WebPluginImpl::stopFind() { + if (delegate_) + delegate_->StopFind(); +} + + +// ----------------------------------------------------------------------------- + +WebPluginImpl::WebPluginImpl( + WebFrame* webframe, + const WebPluginParams& params, + const FilePath& file_path, + const std::string& mime_type, + const base::WeakPtr<WebPluginPageDelegate>& page_delegate) + : windowless_(false), + window_(gfx::kNullPluginWindow), + accepts_input_events_(false), + page_delegate_(page_delegate), + webframe_(webframe), + delegate_(NULL), + container_(NULL), + plugin_url_(params.url), + load_manually_(params.loadManually), + first_geometry_update_(true), + ignore_response_error_(false), + file_path_(file_path), + mime_type_(mime_type), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { + DCHECK_EQ(params.attributeNames.size(), params.attributeValues.size()); + StringToLowerASCII(&mime_type_); + + for (size_t i = 0; i < params.attributeNames.size(); ++i) { + arg_names_.push_back(params.attributeNames[i].utf8()); + arg_values_.push_back(params.attributeValues[i].utf8()); + } +} + +WebPluginImpl::~WebPluginImpl() { +} + +void WebPluginImpl::SetWindow(gfx::PluginWindowHandle window) { +#if defined(OS_MACOSX) + // The only time this is called twice, and the second time with a + // non-zero PluginWindowHandle, is the case when this WebPluginImpl + // is created on behalf of the GPU plugin. This entire code path + // will go away soon, as soon as the GPU plugin becomes the GPU + // process, so it is being separated out for easy deletion. + + // The logic we want here is: if (window) DCHECK(!window_); + DCHECK(!(window_ && window)); + window_ = window; + // Lie to ourselves about being windowless even if we got a fake + // plugin window handle, so we continue to get input events. + windowless_ = true; + accepts_input_events_ = true; + // We do not really need to notify the page delegate that a plugin + // window was created -- so don't. +#else + if (window) { + DCHECK(!windowless_); + window_ = window; + accepts_input_events_ = false; + if (page_delegate_) { + // Tell the view delegate that the plugin window was created, so that it + // can create necessary container widgets. + page_delegate_->CreatedPluginWindow(window); + } + } else { + DCHECK(!window_); // Make sure not called twice. + windowless_ = true; + accepts_input_events_ = true; + } +#endif +} + +void WebPluginImpl::WillDestroyWindow(gfx::PluginWindowHandle window) { + DCHECK_EQ(window, window_); + window_ = gfx::kNullPluginWindow; + if (page_delegate_) + page_delegate_->WillDestroyPluginWindow(window); +} + +GURL WebPluginImpl::CompleteURL(const char* url) { + if (!webframe_) { + NOTREACHED(); + return GURL(); + } + // TODO(darin): Is conversion from UTF8 correct here? + return webframe_->document().completeURL(WebString::fromUTF8(url)); +} + +void WebPluginImpl::CancelResource(unsigned long id) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].id == id) { + if (clients_[i].loader.get()) { + clients_[i].loader->setDefersLoading(false); + clients_[i].loader->cancel(); + RemoveClient(i); + } + return; + } + } +} + +bool WebPluginImpl::SetPostData(WebURLRequest* request, + const char *buf, + uint32 length) { + std::vector<std::string> names; + std::vector<std::string> values; + std::vector<char> body; + bool rv = NPAPI::PluginHost::SetPostData(buf, length, &names, &values, &body); + + for (size_t i = 0; i < names.size(); ++i) { + request->addHTTPHeaderField(WebString::fromUTF8(names[i]), + WebString::fromUTF8(values[i])); + } + + WebString content_type_header = WebString::fromUTF8("Content-Type"); + const WebString& content_type = + request->httpHeaderField(content_type_header); + if (content_type.isEmpty()) { + request->setHTTPHeaderField( + content_type_header, + WebString::fromUTF8("application/x-www-form-urlencoded")); + } + + WebHTTPBody http_body; + if (body.size()) { + http_body.initialize(); + http_body.appendData(WebData(&body[0], body.size())); + } + request->setHTTPBody(http_body); + + return rv; +} + +bool WebPluginImpl::IsValidUrl(const GURL& url, Referrer referrer_flag) { + if (referrer_flag == PLUGIN_SRC && + mime_type_ == "application/x-shockwave-flash" && + url.GetOrigin() != plugin_url_.GetOrigin()) { + // Do url check to make sure that there are no @, ;, \ chars in between url + // scheme and url path. + const char* url_to_check(url.spec().data()); + url_parse::Parsed parsed; + url_parse::ParseStandardURL(url_to_check, strlen(url_to_check), &parsed); + if (parsed.path.begin <= parsed.scheme.end()) + return true; + std::string string_to_search; + string_to_search.assign(url_to_check + parsed.scheme.end(), + parsed.path.begin - parsed.scheme.end()); + if (string_to_search.find("@") != std::string::npos || + string_to_search.find(";") != std::string::npos || + string_to_search.find("\\") != std::string::npos) + return false; + } + + return true; +} + +WebPluginImpl::RoutingStatus WebPluginImpl::RouteToFrame( + const char* url, + bool is_javascript_url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + Referrer referrer_flag) { + // If there is no target, there is nothing to do + if (!target) + return NOT_ROUTED; + + // This could happen if the WebPluginContainer was already deleted. + if (!webframe_) + return NOT_ROUTED; + + WebString target_str = WebString::fromUTF8(target); + + // Take special action for JavaScript URLs + if (is_javascript_url) { + WebFrame* target_frame = + webframe_->view()->findFrameByName(target_str, webframe_); + // For security reasons, do not allow JavaScript on frames + // other than this frame. + if (target_frame != webframe_) { + // TODO(darin): Localize this message. + const char kMessage[] = + "Ignoring cross-frame javascript URL load requested by plugin."; + webframe_->addMessageToConsole( + WebConsoleMessage(WebConsoleMessage::LevelError, + WebString::fromUTF8(kMessage))); + return ROUTED; + } + + // Route javascript calls back to the plugin. + return NOT_ROUTED; + } + + // If we got this far, we're routing content to a target frame. + // Go fetch the URL. + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, referrer_flag)) + return INVALID_URL; + + if (strcmp(method, "GET") != 0) { + // We're only going to route HTTP/HTTPS requests + if (!(complete_url.SchemeIs("http") || complete_url.SchemeIs("https"))) + return INVALID_URL; + } + + WebURLRequest request(complete_url); + SetReferrer(&request, referrer_flag); + + request.setHTTPMethod(WebString::fromUTF8(method)); + request.setFirstPartyForCookies( + webframe_->document().firstPartyForCookies()); + if (len > 0) { + if (!SetPostData(&request, buf, len)) { + // Uhoh - we're in trouble. There isn't a good way + // to recover at this point. Break out. + NOTREACHED(); + return ROUTED; + } + } + + container_->loadFrameRequest( + request, target_str, notify_id != 0, reinterpret_cast<void*>(notify_id)); + return ROUTED; +} + +NPObject* WebPluginImpl::GetWindowScriptNPObject() { + if (!webframe_) { + NOTREACHED(); + return NULL; + } + return webframe_->windowObject(); +} + +NPObject* WebPluginImpl::GetPluginElement() { + return container_->scriptableObjectForElement(); +} + +void WebPluginImpl::SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie) { + if (!page_delegate_) + return; + + WebCookieJar* cookie_jar = page_delegate_->GetCookieJar(); + if (!cookie_jar) { + DLOG(WARNING) << "No cookie jar!"; + return; + } + + cookie_jar->setCookie( + url, first_party_for_cookies, WebString::fromUTF8(cookie)); +} + +std::string WebPluginImpl::GetCookies(const GURL& url, + const GURL& first_party_for_cookies) { + if (!page_delegate_) + return std::string(); + + WebCookieJar* cookie_jar = page_delegate_->GetCookieJar(); + if (!cookie_jar) { + DLOG(WARNING) << "No cookie jar!"; + return std::string(); + } + + return UTF16ToUTF8(cookie_jar->cookies(url, first_party_for_cookies)); +} + +void WebPluginImpl::ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval) { + if (page_delegate_) { + page_delegate_->ShowModalHTMLDialogForPlugin( + url, gfx::Size(width, height), json_arguments, json_retval); + } +} + +void WebPluginImpl::OnMissingPluginStatus(int status) { + NOTREACHED(); +} + +void WebPluginImpl::Invalidate() { + if (container_) + container_->invalidate(); +} + +void WebPluginImpl::InvalidateRect(const gfx::Rect& rect) { + if (container_) + container_->invalidateRect(rect); +} + +void WebPluginImpl::OnDownloadPluginSrcUrl() { + HandleURLRequestInternal( + plugin_url_.spec().c_str(), "GET", NULL, NULL, 0, 0, false, DOCUMENT_URL); +} + +WebPluginResourceClient* WebPluginImpl::GetClientFromLoader( + WebURLLoader* loader) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + return client_info->client; + return NULL; +} + +WebPluginImpl::ClientInfo* WebPluginImpl::GetClientInfoFromLoader( + WebURLLoader* loader) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) + return &clients_[i]; + } + + NOTREACHED(); + return 0; +} + +void WebPluginImpl::willSendRequest(WebURLLoader* loader, + WebURLRequest& request, + const WebURLResponse&) { + WebPluginResourceClient* client = GetClientFromLoader(loader); + if (client) + client->WillSendRequest(request.url()); +} + +void WebPluginImpl::didSendData(WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { +} + +void WebPluginImpl::didReceiveResponse(WebURLLoader* loader, + const WebURLResponse& response) { + static const int kHttpPartialResponseStatusCode = 206; + static const int kHttpResponseSuccessStatusCode = 200; + + WebPluginResourceClient* client = GetClientFromLoader(loader); + if (!client) + return; + + ResponseInfo response_info; + GetResponseInfo(response, &response_info); + + bool request_is_seekable = true; + if (client->IsMultiByteResponseExpected()) { + if (response.httpStatusCode() == kHttpPartialResponseStatusCode) { + HandleHttpMultipartResponse(response, client); + return; + } else if (response.httpStatusCode() == kHttpResponseSuccessStatusCode) { + // If the client issued a byte range request and the server responds with + // HTTP 200 OK, it indicates that the server does not support byte range + // requests. + // We need to emulate Firefox behavior by doing the following:- + // 1. Destroy the plugin instance in the plugin process. Ensure that + // existing resource requests initiated for the plugin instance + // continue to remain valid. + // 2. Create a new plugin instance and notify it about the response + // received here. + if (!ReinitializePluginForResponse(loader)) { + NOTREACHED(); + return; + } + + // The server does not support byte range requests. No point in creating + // seekable streams. + request_is_seekable = false; + + delete client; + client = NULL; + + // Create a new resource client for this request. + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) { + WebPluginResourceClient* resource_client = + delegate_->CreateResourceClient(clients_[i].id, plugin_url_, 0); + clients_[i].client = resource_client; + client = resource_client; + break; + } + } + + DCHECK(client != NULL); + } + } + + // Calling into a plugin could result in reentrancy if the plugin yields + // control to the OS like entering a modal loop etc. Prevent this by + // stopping further loading until the plugin notifies us that it is ready to + // accept data + loader->setDefersLoading(true); + + client->DidReceiveResponse( + response_info.mime_type, + GetAllHeaders(response), + response_info.expected_length, + response_info.last_modified, + request_is_seekable); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + devtools_agent->didReceiveResponse(client_info->id, response); + } + + // Bug http://b/issue?id=925559. The flash plugin would not handle the HTTP + // error codes in the stream header and as a result, was unaware of the + // fate of the HTTP requests issued via NPN_GetURLNotify. Webkit and FF + // destroy the stream and invoke the NPP_DestroyStream function on the + // plugin if the HTTP request fails. + const GURL& url = response.url(); + if (url.SchemeIs("http") || url.SchemeIs("https")) { + if (response.httpStatusCode() < 100 || response.httpStatusCode() >= 400) { + // The plugin instance could be in the process of deletion here. + // Verify if the WebPluginResourceClient instance still exists before + // use. + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) { + client_info->pending_failure_notification = true; + } + } + } +} + +void WebPluginImpl::didReceiveData(WebURLLoader* loader, + const char *buffer, + int length) { + WebPluginResourceClient* client = GetClientFromLoader(loader); + if (!client) + return; + + // ClientInfo can be removed from clients_ vector by next statements. + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info) + devtools_agent->didReceiveData(client_info->id, length); + } + MultiPartResponseHandlerMap::iterator index = + multi_part_response_map_.find(client); + if (index != multi_part_response_map_.end()) { + MultipartResponseDelegate* multi_part_handler = (*index).second; + DCHECK(multi_part_handler != NULL); + multi_part_handler->OnReceivedData(buffer, length); + } else { + loader->setDefersLoading(true); + client->DidReceiveData(buffer, length, 0); + } +} + +void WebPluginImpl::didFinishLoading(WebURLLoader* loader) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info && client_info->client) { + MultiPartResponseHandlerMap::iterator index = + multi_part_response_map_.find(client_info->client); + if (index != multi_part_response_map_.end()) { + delete (*index).second; + multi_part_response_map_.erase(index); + if (page_delegate_) + page_delegate_->DidStopLoadingForPlugin(); + } + loader->setDefersLoading(true); + WebPluginResourceClient* resource_client = client_info->client; + // The ClientInfo can get deleted in the call to DidFinishLoading below. + // It is not safe to access this structure after that. + client_info->client = NULL; + resource_client->DidFinishLoading(); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFinishLoading(client_info->id); + } +} + +void WebPluginImpl::didFail(WebURLLoader* loader, + const WebURLError& error) { + ClientInfo* client_info = GetClientInfoFromLoader(loader); + if (client_info && client_info->client) { + loader->setDefersLoading(true); + WebPluginResourceClient* resource_client = client_info->client; + // The ClientInfo can get deleted in the call to DidFail below. + // It is not safe to access this structure after that. + client_info->client = NULL; + resource_client->DidFail(); + + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFailLoading(client_info->id, error); + } +} + +void WebPluginImpl::RemoveClient(size_t i) { + clients_.erase(clients_.begin() + i); +} + +void WebPluginImpl::RemoveClient(WebURLLoader* loader) { + for (size_t i = 0; i < clients_.size(); ++i) { + if (clients_[i].loader.get() == loader) { + RemoveClient(i); + return; + } + } +} + +void WebPluginImpl::SetContainer(WebPluginContainer* container) { + if (!container) + TearDownPluginInstance(NULL); + container_ = container; +} + +void WebPluginImpl::HandleURLRequest(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed) { + // GetURL/PostURL requests initiated explicitly by plugins should specify the + // plugin SRC url as the referrer if it is available. + HandleURLRequestInternal( + url, method, target, buf, len, notify_id, popups_allowed, PLUGIN_SRC); +} + +void WebPluginImpl::HandleURLRequestInternal(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + Referrer referrer_flag) { + // For this request, we either route the output to a frame + // because a target has been specified, or we handle the request + // here, i.e. by executing the script if it is a javascript url + // or by initiating a download on the URL, etc. There is one special + // case in that the request is a javascript url and the target is "_self", + // in which case we route the output to the plugin rather than routing it + // to the plugin's frame. + bool is_javascript_url = StartsWithASCII(url, "javascript:", false); + RoutingStatus routing_status = RouteToFrame( + url, is_javascript_url, method, target, buf, len, notify_id, + referrer_flag); + if (routing_status == ROUTED) + return; + + if (is_javascript_url) { + GURL gurl(url); + WebString result = container_->executeScriptURL(gurl, popups_allowed); + + // delegate_ could be NULL because executeScript caused the container to + // be deleted. + if (delegate_) { + delegate_->SendJavaScriptStream( + gurl, result.utf8(), !result.isNull(), notify_id); + } + + return; + } + + unsigned long resource_id = GetNextResourceId(); + if (!resource_id) + return; + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, referrer_flag)) + return; + + WebPluginResourceClient* resource_client = delegate_->CreateResourceClient( + resource_id, complete_url, notify_id); + if (!resource_client) + return; + + // If the RouteToFrame call returned a failure then inform the result + // back to the plugin asynchronously. + if ((routing_status == INVALID_URL) || + (routing_status == GENERAL_FAILURE)) { + resource_client->DidFail(); + return; + } + + // CreateResourceClient() sends a synchronous IPC message so it's possible + // that TearDownPluginInstance() may have been called in the nested + // message loop. If so, don't start the request. + if (!delegate_) + return; + + InitiateHTTPRequest(resource_id, resource_client, complete_url, method, buf, + len, NULL, referrer_flag); +} + +unsigned long WebPluginImpl::GetNextResourceId() { + if (!webframe_) + return 0; + WebView* view = webframe_->view(); + if (!view) + return 0; + return view->createUniqueIdentifierForRequest(); +} + +bool WebPluginImpl::InitiateHTTPRequest(unsigned long resource_id, + WebPluginResourceClient* client, + const GURL& url, + const char* method, + const char* buf, + int buf_len, + const char* range_info, + Referrer referrer_flag) { + if (!client) { + NOTREACHED(); + return false; + } + + ClientInfo info; + info.id = resource_id; + info.client = client; + info.request.initialize(); + info.request.setURL(url); + info.request.setFirstPartyForCookies( + webframe_->document().firstPartyForCookies()); + info.request.setRequestorProcessID(delegate_->GetProcessId()); + info.request.setTargetType(WebURLRequest::TargetIsObject); + info.request.setHTTPMethod(WebString::fromUTF8(method)); + info.pending_failure_notification = false; + + if (range_info) { + info.request.addHTTPHeaderField(WebString::fromUTF8("Range"), + WebString::fromUTF8(range_info)); + } + + if (strcmp(method, "POST") == 0) { + // Adds headers or form data to a request. This must be called before + // we initiate the actual request. + SetPostData(&info.request, buf, buf_len); + } + + SetReferrer(&info.request, referrer_flag); + + // Sets the routing id to associate the ResourceRequest with the RenderView. + webframe_->dispatchWillSendRequest(info.request); + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) { + devtools_agent->identifierForInitialRequest(resource_id, webframe_, + info.request); + devtools_agent->willSendRequest(resource_id, info.request); + } + + info.loader.reset(WebKit::webKitClient()->createURLLoader()); + if (!info.loader.get()) + return false; + info.loader->loadAsynchronously(info.request, this); + + clients_.push_back(info); + return true; +} + +void WebPluginImpl::CancelDocumentLoad() { + if (webframe_) { + ignore_response_error_ = true; + webframe_->stopLoading(); + } +} + +void WebPluginImpl::InitiateHTTPRangeRequest( + const char* url, const char* range_info, int range_request_id) { + unsigned long resource_id = GetNextResourceId(); + if (!resource_id) + return; + + GURL complete_url = CompleteURL(url); + // Remove when flash bug is fixed. http://crbug.com/40016. + if (!WebPluginImpl::IsValidUrl(complete_url, + load_manually_ ? NO_REFERRER : PLUGIN_SRC)) + return; + + WebPluginResourceClient* resource_client = + delegate_->CreateSeekableResourceClient(resource_id, range_request_id); + InitiateHTTPRequest( + resource_id, resource_client, complete_url, "GET", NULL, 0, range_info, + load_manually_ ? NO_REFERRER : PLUGIN_SRC); +} + +void WebPluginImpl::SetDeferResourceLoading(unsigned long resource_id, + bool defer) { + std::vector<ClientInfo>::iterator client_index = clients_.begin(); + while (client_index != clients_.end()) { + ClientInfo& client_info = *client_index; + + if (client_info.id == resource_id) { + client_info.loader->setDefersLoading(defer); + + // If we determined that the request had failed via the HTTP headers + // in the response then we send out a failure notification to the + // plugin process, as certain plugins don't handle HTTP failure codes + // correctly. + if (!defer && client_info.client && + client_info.pending_failure_notification) { + // The ClientInfo and the iterator can become invalid due to the call + // to DidFail below. + WebPluginResourceClient* resource_client = client_info.client; + client_info.loader->cancel(); + clients_.erase(client_index++); + resource_client->DidFail(); + + // Report that resource loading finished. + if (WebDevToolsAgent* devtools_agent = GetDevToolsAgent()) + devtools_agent->didFinishLoading(resource_id); + } + break; + } + client_index++; + } +} + +void WebPluginImpl::HandleHttpMultipartResponse( + const WebURLResponse& response, WebPluginResourceClient* client) { + std::string multipart_boundary; + if (!MultipartResponseDelegate::ReadMultipartBoundary( + response, &multipart_boundary)) { + NOTREACHED(); + return; + } + + if (page_delegate_) + page_delegate_->DidStartLoadingForPlugin(); + + MultiPartResponseClient* multi_part_response_client = + new MultiPartResponseClient(client); + + MultipartResponseDelegate* multi_part_response_handler = + new MultipartResponseDelegate(multi_part_response_client, NULL, + response, + multipart_boundary); + multi_part_response_map_[client] = multi_part_response_handler; +} + +bool WebPluginImpl::ReinitializePluginForResponse( + WebURLLoader* loader) { + WebFrame* webframe = webframe_; + if (!webframe) + return false; + + WebView* webview = webframe->view(); + if (!webview) + return false; + + WebPluginContainer* container_widget = container_; + + // Destroy the current plugin instance. + TearDownPluginInstance(loader); + + container_ = container_widget; + webframe_ = webframe; + + WebPluginDelegate* plugin_delegate = page_delegate_->CreatePluginDelegate( + file_path_, mime_type_); + + bool ok = plugin_delegate && plugin_delegate->Initialize( + plugin_url_, arg_names_, arg_values_, this, load_manually_); + + if (!ok) { + container_ = NULL; + // TODO(iyengar) Should we delete the current plugin instance here? + return false; + } + + delegate_ = plugin_delegate; + + // Force a geometry update to occur to ensure that the plugin becomes + // visible. + container_->reportGeometry(); + + // The plugin move sequences accumulated via DidMove are sent to the browser + // whenever the renderer paints. Force a paint here to ensure that changes + // to the plugin window are propagated to the browser. + container_->invalidate(); + return true; +} + +void WebPluginImpl::TearDownPluginInstance( + WebURLLoader* loader_to_ignore) { + // The container maintains a list of JSObjects which are related to this + // plugin. Tell the frame we're gone so that it can invalidate all of + // those sub JSObjects. + if (container_) + container_->clearScriptObjects(); + + if (delegate_) { + // Call PluginDestroyed() first to prevent the plugin from calling us back + // in the middle of tearing down the render tree. + delegate_->PluginDestroyed(); + delegate_ = NULL; + } + + // Cancel any pending requests because otherwise this deleted object will + // be called by the ResourceDispatcher. + std::vector<ClientInfo>::iterator client_index = clients_.begin(); + while (client_index != clients_.end()) { + ClientInfo& client_info = *client_index; + + if (loader_to_ignore == client_info.loader) { + client_index++; + continue; + } + + if (client_info.loader.get()) + client_info.loader->cancel(); + + client_index = clients_.erase(client_index); + } + + // This needs to be called now and not in the destructor since the + // webframe_ might not be valid anymore. + webframe_ = NULL; + method_factory_.RevokeAll(); +} + +void WebPluginImpl::SetReferrer(WebKit::WebURLRequest* request, + Referrer referrer_flag) { + switch (referrer_flag) { + case DOCUMENT_URL: + webframe_->setReferrerForRequest(*request, GURL()); + break; + + case PLUGIN_SRC: + webframe_->setReferrerForRequest(*request, plugin_url_); + break; + + default: + break; + } +} + +WebDevToolsAgent* WebPluginImpl::GetDevToolsAgent() { + if (!webframe_) + return NULL; + WebView* view = webframe_->view(); + if (!view) + return NULL; + return view->devToolsAgent(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/plugins/webplugin_impl.h b/webkit/glue/plugins/webplugin_impl.h new file mode 100644 index 0000000..9b75e6e --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl.h @@ -0,0 +1,332 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ +#define WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ + +#include <string> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/linked_ptr.h" +#include "base/task.h" +#include "base/weak_ptr.h" +#include "gfx/native_widget_types.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPlugin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/plugins/webplugin.h" + +class WebViewDelegate; + +namespace WebKit { +class WebDevToolsAgent; +class WebFrame; +class WebPluginContainer; +class WebURLResponse; +class WebURLLoader; +class WebURLRequest; +} + +namespace webkit_glue { + +class MultipartResponseDelegate; +class WebPluginDelegate; +class WebPluginPageDelegate; + +// This is the WebKit side of the plugin implementation that forwards calls, +// after changing out of WebCore types, to a delegate. The delegate may +// be in a different process. +class WebPluginImpl : public WebPlugin, + public WebKit::WebPlugin, + public WebKit::WebURLLoaderClient { + public: + WebPluginImpl( + WebKit::WebFrame* frame, + const WebKit::WebPluginParams& params, + const FilePath& file_path, + const std::string& mime_type, + const base::WeakPtr<WebPluginPageDelegate>& page_delegate); + virtual ~WebPluginImpl(); + + // Helper function for sorting post data. + static bool SetPostData(WebKit::WebURLRequest* request, + const char* buf, + uint32 length); + + virtual WebPluginDelegate* delegate() { return delegate_; } + + private: + // WebKit::WebPlugin methods: + virtual bool initialize( + WebKit::WebPluginContainer* container); + virtual void destroy(); + virtual NPObject* scriptableObject(); + virtual void paint( + WebKit::WebCanvas* canvas, const WebKit::WebRect& paint_rect); + virtual void updateGeometry( + const WebKit::WebRect& frame_rect, const WebKit::WebRect& clip_rect, + const WebKit::WebVector<WebKit::WebRect>& cut_outs, bool is_visible); + virtual void updateFocus(bool focused); + virtual void updateVisibility(bool visible); + virtual bool acceptsInputEvents(); + virtual bool handleInputEvent( + const WebKit::WebInputEvent& event, WebKit::WebCursorInfo& cursor_info); + virtual void didReceiveResponse(const WebKit::WebURLResponse& response); + virtual void didReceiveData(const char* data, int data_length); + virtual void didFinishLoading(); + virtual void didFailLoading(const WebKit::WebURLError& error); + virtual void didFinishLoadingFrameRequest( + const WebKit::WebURL& url, void* notify_data); + virtual void didFailLoadingFrameRequest( + const WebKit::WebURL& url, void* notify_data, + const WebKit::WebURLError& error); + virtual bool supportsPaginatedPrint(); + virtual int printBegin(const WebKit::WebRect& printable_area, + int printer_dpi); + virtual bool printPage(int page_number, WebKit::WebCanvas* canvas); + virtual void printEnd(); + virtual bool hasSelection() const; + virtual WebKit::WebString selectionAsText() const; + virtual WebKit::WebString selectionAsMarkup() const; + virtual void setZoomFactor(float scale, bool text_only); + virtual bool startFind(const WebKit::WebString& search_text, + bool case_sensitive, + int identifier); + virtual void selectFindResult(bool forward); + virtual void stopFind(); + + // WebPlugin implementation: + void SetWindow(gfx::PluginWindowHandle window); + virtual void SetAcceptsInputEvents(bool accepts) { + accepts_input_events_ = accepts; + } + void WillDestroyWindow(gfx::PluginWindowHandle window); +#if defined(OS_WIN) + void SetWindowlessPumpEvent(HANDLE pump_messages_event) { } +#endif + virtual void CancelResource(unsigned long id); + virtual void Invalidate(); + virtual void InvalidateRect(const gfx::Rect& rect); + virtual NPObject* GetWindowScriptNPObject(); + virtual NPObject* GetPluginElement(); + virtual void SetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie); + virtual std::string GetCookies(const GURL& url, + const GURL& first_party_for_cookies); + virtual void ShowModalHTMLDialog(const GURL& url, int width, int height, + const std::string& json_arguments, + std::string* json_retval); + virtual void OnMissingPluginStatus(int status); + + // Given a (maybe partial) url, completes using the base url. + GURL CompleteURL(const char* url); + + // Executes the script passed in. The notify_needed and notify_data arguments + // are passed in by the plugin process. These indicate whether the plugin + // expects a notification on script execution. We pass them back to the + // plugin as is. This avoids having to track the notification arguments in + // the plugin process. + bool ExecuteScript(const std::string& url, const std::wstring& script, + bool notify_needed, intptr_t notify_data, + bool popups_allowed); + + enum RoutingStatus { + ROUTED, + NOT_ROUTED, + INVALID_URL, + GENERAL_FAILURE + }; + + // Determines the referrer value sent along with outgoing HTTP requests + // issued by plugins. + enum Referrer { + PLUGIN_SRC, + DOCUMENT_URL, + NO_REFERRER + }; + + // Given a download request, check if we need to route the output to a frame. + // Returns ROUTED if the load is done and routed to a frame, NOT_ROUTED or + // corresponding error codes otherwise. + RoutingStatus RouteToFrame(const char* url, + bool is_javascript_url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + Referrer referrer_flag); + + // Returns the next avaiable resource id. Returns 0 if the operation fails. + // It may fail if the page has already been closed. + unsigned long GetNextResourceId(); + + // Initiates HTTP GET/POST requests. + // Returns true on success. + bool InitiateHTTPRequest(unsigned long resource_id, + WebPluginResourceClient* client, + const GURL& url, + const char* method, + const char* buf, + int len, + const char* range_info, + Referrer referrer_flag); + + gfx::Rect GetWindowClipRect(const gfx::Rect& rect); + + // Sets the actual Widget for the plugin. + void SetContainer(WebKit::WebPluginContainer* container); + + // Destroys the plugin instance. + // The response_handle_to_ignore parameter if not NULL indicates the + // resource handle to be left valid during plugin shutdown. + void TearDownPluginInstance(WebKit::WebURLLoader* loader_to_ignore); + + // WebURLLoaderClient implementation. We implement this interface in the + // renderer process, and then use the simple WebPluginResourceClient interface + // to relay the callbacks to the plugin. + virtual void willSendRequest(WebKit::WebURLLoader* loader, + WebKit::WebURLRequest& request, + const WebKit::WebURLResponse&); + virtual void didSendData(WebKit::WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent); + virtual void didReceiveResponse(WebKit::WebURLLoader* loader, + const WebKit::WebURLResponse& response); + virtual void didReceiveData(WebKit::WebURLLoader* loader, const char *buffer, + int length); + virtual void didFinishLoading(WebKit::WebURLLoader* loader); + virtual void didFail(WebKit::WebURLLoader* loader, + const WebKit::WebURLError& error); + + // Helper function to remove the stored information about a resource + // request given its index in m_clients. + void RemoveClient(size_t i); + + // Helper function to remove the stored information about a resource + // request given a handle. + void RemoveClient(WebKit::WebURLLoader* loader); + + void HandleURLRequest(const char* url, + const char *method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed); + + void CancelDocumentLoad(); + + void InitiateHTTPRangeRequest( + const char* url, const char* range_info, int pending_request_id); + + void SetDeferResourceLoading(unsigned long resource_id, bool defer); + + // Ignore in-process plugins mode for this flag. + bool IsOffTheRecord() { return false; } + + // Handles HTTP multipart responses, i.e. responses received with a HTTP + // status code of 206. + void HandleHttpMultipartResponse(const WebKit::WebURLResponse& response, + WebPluginResourceClient* client); + + void HandleURLRequestInternal(const char* url, + const char* method, + const char* target, + const char* buf, + unsigned int len, + int notify_id, + bool popups_allowed, + Referrer referrer_flag); + + // Tears down the existing plugin instance and creates a new plugin instance + // to handle the response identified by the loader parameter. + bool ReinitializePluginForResponse(WebKit::WebURLLoader* loader); + + // Delayed task for downloading the plugin source URL. + void OnDownloadPluginSrcUrl(); + + struct ClientInfo { + unsigned long id; + WebPluginResourceClient* client; + WebKit::WebURLRequest request; + bool pending_failure_notification; + linked_ptr<WebKit::WebURLLoader> loader; + }; + + // Helper functions + WebPluginResourceClient* GetClientFromLoader(WebKit::WebURLLoader* loader); + ClientInfo* GetClientInfoFromLoader(WebKit::WebURLLoader* loader); + + // Helper function to set the referrer on the request passed in. + void SetReferrer(WebKit::WebURLRequest* request, Referrer referrer_flag); + + // Returns DevToolsAgent for the frame or 0. + WebKit::WebDevToolsAgent* GetDevToolsAgent(); + + // Check for invalid chars like @, ;, \ before the first / (in path). + bool IsValidUrl(const GURL& url, Referrer referrer_flag); + + std::vector<ClientInfo> clients_; + + bool windowless_; + gfx::PluginWindowHandle window_; + bool accepts_input_events_; + base::WeakPtr<WebPluginPageDelegate> page_delegate_; + WebKit::WebFrame* webframe_; + + WebPluginDelegate* delegate_; + + // This is just a weak reference. + WebKit::WebPluginContainer* container_; + + typedef std::map<WebPluginResourceClient*, + webkit_glue::MultipartResponseDelegate*> + MultiPartResponseHandlerMap; + // Tracks HTTP multipart response handlers instantiated for + // a WebPluginResourceClient instance. + MultiPartResponseHandlerMap multi_part_response_map_; + + // The plugin source URL. + GURL plugin_url_; + + // Indicates if the download would be initiated by the plugin or us. + bool load_manually_; + + // Indicates if this is the first geometry update received by the plugin. + bool first_geometry_update_; + + // Set to true if the next response error should be ignored. + bool ignore_response_error_; + + // The current plugin geometry and clip rectangle. + WebPluginGeometry geometry_; + + // The location of the plugin on disk. + FilePath file_path_; + + // The mime type of the plugin. + std::string mime_type_; + + // Holds the list of argument names and values passed to the plugin. We keep + // these so that we can re-initialize the plugin if we need to. + std::vector<std::string> arg_names_; + std::vector<std::string> arg_values_; + + ScopedRunnableMethodFactory<WebPluginImpl> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(WebPluginImpl); +}; + +} // namespace webkit_glue + +#endif // #ifndef WEBKIT_GLUE_WEBPLUGIN_IMPL_H_ diff --git a/webkit/glue/plugins/webplugin_impl_unittest.cc b/webkit/glue/plugins/webplugin_impl_unittest.cc new file mode 100644 index 0000000..e70e39a --- /dev/null +++ b/webkit/glue/plugins/webplugin_impl_unittest.cc @@ -0,0 +1,232 @@ +// Copyright (c) 2010 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/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "webkit/glue/plugins/webplugin_impl.h" + +using WebKit::WebHTTPBody; +using WebKit::WebString; +using WebKit::WebURLRequest; +using webkit_glue::WebPluginImpl; + +namespace { + +class WebPluginImplTest : public testing::Test { +}; + +} + +static std::string GetHeader(const WebURLRequest& request, const char* name) { + std::string result; + TrimWhitespace( + request.httpHeaderField(WebString::fromUTF8(name)).utf8(), + TRIM_ALL, + &result); + return result; +} + +static std::string GetBodyText(const WebURLRequest& request) { + const WebHTTPBody& body = request.httpBody(); + if (body.isNull()) + return std::string(); + + std::string result; + size_t i = 0; + WebHTTPBody::Element element; + while (body.elementAt(i++, element)) { + if (element.type == WebHTTPBody::Element::TypeData) { + result.append(element.data.data(), element.data.size()); + } else { + NOTREACHED() << "unexpected element type encountered!"; + } + } + return result; +} + +// The Host functions for NPN_PostURL and NPN_PostURLNotify +// need to parse out some HTTP headers. Make sure it works +// with the following tests + +TEST(WebPluginImplTest, PostParserSimple) { + // Test a simple case with headers & data + const char *ex1 = "foo: bar\nContent-length: 10\n\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ("abcdefghij", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserLongHeader) { + // Test a simple case with long headers + const char *ex1 = "foo: 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\n\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(100U, GetHeader(request, "foo").length()); +} + +TEST(WebPluginImplTest, PostParserManyHeaders) { + // Test a simple case with long headers + const char *ex1 = "h1:h1\nh2:h2\nh3:h3\nh4:h4\nh5:h5\nh6:h6\nh7:h7\nh8:h8\nh9:h9\nh10:h10\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("h1", GetHeader(request, "h1")); + EXPECT_EQ("h2", GetHeader(request, "h2")); + EXPECT_EQ("h3", GetHeader(request, "h3")); + EXPECT_EQ("h4", GetHeader(request, "h4")); + EXPECT_EQ("h5", GetHeader(request, "h5")); + EXPECT_EQ("h6", GetHeader(request, "h6")); + EXPECT_EQ("h7", GetHeader(request, "h7")); + EXPECT_EQ("h8", GetHeader(request, "h8")); + EXPECT_EQ("h9", GetHeader(request, "h9")); + EXPECT_EQ("h10", GetHeader(request, "h10")); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserDuplicateHeaders) { + // Test a simple case with long headers + // What value gets returned doesn't really matter. It shouldn't error + // out. + const char *ex1 = "h1:h1\nh1:h2\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserNoHeaders) { + // Test a simple case with no headers but with data + const char *ex1 = "\nabcdefghij"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(0U, GetHeader(request, "foo").length()); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ("abcdefghij", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserNoBody) { + // Test a simple case with headers and no body + const char *ex1 = "Foo:bar\n\n"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + EXPECT_EQ(0U, GetBodyText(request).length()); +} + +TEST(WebPluginImplTest, PostParserBodyWithNewLines) { + // Test a simple case with headers and no body + const char *ex1 = "Foo:bar\n\n\n\nabcdefg\n\nabcdefg"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ(GetBodyText(request), "\n\nabcdefg\n\nabcdefg"); +} + +TEST(WebPluginImplTest, PostParserErrorNoBody) { + // Test with headers and no body + const char *ex1 = "Foo:bar\n"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserErrorEmpty) { + // Test with an empty string + const char *ex1 = ""; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); +} + +TEST(WebPluginImplTest, PostParserEmptyName) { + // Test an error case with an empty header name field + const char *ex1 = "foo:bar\n:blat\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserEmptyValue) { + // Test an error case with an empty value field + const char *ex1 = "foo:bar\nbar:\n\nbody"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ("body", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserCRLF) { + // Test an error case with an empty value field + const char *ex1 = "foo: bar\r\nbar:\r\n\r\nbody\r\n\r\nbody2"; + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + static_cast<uint32>(strlen(ex1))); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ("body\r\n\r\nbody2", GetBodyText(request)); +} + +TEST(WebPluginImplTest, PostParserBodyWithBinaryData) { + // Test a simple case with headers and binary data. + char ex1[33] = "foo: bar\nContent-length: 10\n\n"; + unsigned int binary_data = 0xFFFFFFF0; + memcpy(ex1 + strlen("foo: bar\nContent-length: 10\n\n"), &binary_data, + sizeof(binary_data)); + + WebURLRequest request; + request.initialize(); + bool rv = WebPluginImpl::SetPostData(&request, ex1, + sizeof(ex1)/sizeof(ex1[0])); + EXPECT_EQ(true, rv); + EXPECT_EQ("bar", GetHeader(request, "foo")); + EXPECT_EQ(0U, GetHeader(request, "bar").length()); + EXPECT_EQ(0U, GetHeader(request, "Content-length").length()); + + std::string body = GetBodyText(request); + + EXPECT_EQ(0xF0, (unsigned char)body[0]); + EXPECT_EQ(0xFF, (unsigned char)body[1]); + EXPECT_EQ(0xFF, (unsigned char)body[2]); + EXPECT_EQ(0xFF, (unsigned char)body[3]); +} diff --git a/webkit/glue/plugins/webplugin_page_delegate.h b/webkit/glue/plugins/webplugin_page_delegate.h new file mode 100644 index 0000000..d915fdd --- /dev/null +++ b/webkit/glue/plugins/webplugin_page_delegate.h @@ -0,0 +1,69 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_ +#define WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_ + +#include "gfx/native_widget_types.h" + +class FilePath; +class GURL; + +namespace WebKit { +class WebCookieJar; +} + +namespace webkit_glue { + +class WebPluginDelegate; +struct WebPluginGeometry; + +// Used by the WebPlugin to communicate back to the containing page. +class WebPluginPageDelegate { + public: + // This method is called to create a WebPluginDelegate implementation when a + // new plugin is instanced. See webkit_glue::CreateWebPluginDelegateHelper + // for a default WebPluginDelegate implementation. + virtual WebPluginDelegate* CreatePluginDelegate( + const FilePath& file_path, + const std::string& mime_type) = 0; + + // Called when a windowed plugin is created. + // Lets the view delegate create anything it is using to wrap the plugin. + virtual void CreatedPluginWindow( + gfx::PluginWindowHandle handle) = 0; + + // Called when a windowed plugin is closing. + // Lets the view delegate shut down anything it is using to wrap the plugin. + virtual void WillDestroyPluginWindow( + gfx::PluginWindowHandle handle) = 0; + + // Keeps track of the necessary window move for a plugin window that resulted + // from a scroll operation. That way, all plugin windows can be moved at the + // same time as each other and the page. + virtual void DidMovePlugin( + const WebPluginGeometry& move) = 0; + + // Notifies the parent view that a load has begun. + virtual void DidStartLoadingForPlugin() = 0; + + // Notifies the parent view that all loads are finished. + virtual void DidStopLoadingForPlugin() = 0; + + // Asks the browser to show a modal HTML dialog. The dialog is passed the + // given arguments as a JSON string, and returns its result as a JSON string + // through json_retval. + virtual void ShowModalHTMLDialogForPlugin( + const GURL& url, + const gfx::Size& size, + const std::string& json_arguments, + std::string* json_retval) = 0; + + // The WebCookieJar to use for this plugin. + virtual WebKit::WebCookieJar* GetCookieJar() = 0; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBPLUGIN_PAGE_DELEGATE_H_ diff --git a/webkit/glue/plugins/webplugin_print_delegate.h b/webkit/glue/plugins/webplugin_print_delegate.h new file mode 100644 index 0000000..040e58f --- /dev/null +++ b/webkit/glue/plugins/webplugin_print_delegate.h @@ -0,0 +1,49 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ +#define WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ + +#include "base/basictypes.h" +#include "third_party/npapi/bindings/npapi_extensions.h" + +namespace gfx { +class Rect; +} + +namespace webkit_glue { + +// Interface for the NPAPI print extension. This class implements "NOP" +// versions of all these functions so it can be used seamlessly by the +// "regular" plugin delegate while being overridden by the "pepper" one. +class WebPluginPrintDelegate { + public: + // If a plugin supports print extensions, then it gets to participate fully + // in the browser's print workflow by specifying the number of pages to be + // printed and providing a print output for specified pages. + virtual bool PrintSupportsPrintExtension() { + return false; + } + + // Note: printable_area is in points (a point is 1/72 of an inch). + virtual int PrintBegin(const gfx::Rect& printable_area, int printer_dpi) { + return 0; + } + + virtual bool PrintPage(int page_number, WebKit::WebCanvas* canvas) { + return false; + } + + virtual void PrintEnd() { + } + + protected: + WebPluginPrintDelegate() {} + virtual ~WebPluginPrintDelegate() {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_PLUGINS_WEBPLUGIN_PRINT_DELEGATE_H_ + diff --git a/webkit/glue/plugins/webplugininfo.h b/webkit/glue/plugins/webplugininfo.h new file mode 100644 index 0000000..9d2ab6e --- /dev/null +++ b/webkit/glue/plugins/webplugininfo.h @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_WEBPLUGININFO_H_ +#define WEBKIT_GLUE_WEBPLUGININFO_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" + +// Describes a mime type entry for a plugin. +struct WebPluginMimeType { + // The name of the mime type (e.g., "application/x-shockwave-flash"). + std::string mime_type; + + // A list of all the file extensions for this mime type. + std::vector<std::string> file_extensions; + + // Description of the mime type. + string16 description; +}; + +// Describes an available NPAPI plugin. +struct WebPluginInfo { + // The name of the plugin (i.e. Flash). + string16 name; + + // The path to the plugin file (DLL/bundle/library). + FilePath path; + + // The version number of the plugin file (may be OS-specific) + string16 version; + + // A description of the plugin that we get from its version info. + string16 desc; + + // A list of all the mime types that this plugin supports. + std::vector<WebPluginMimeType> mime_types; + + // Whether the plugin is enabled. + bool enabled; +}; + +#endif // WEBKIT_GLUE_WEBPLUGININFO_H_ diff --git a/webkit/glue/plugins/webview_plugin.cc b/webkit/glue/plugins/webview_plugin.cc new file mode 100644 index 0000000..413ae10 --- /dev/null +++ b/webkit/glue/plugins/webview_plugin.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2010 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 "webkit/glue/plugins/webview_plugin.h" + +#include "base/message_loop.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginContainer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSettings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" + +#if WEBKIT_USING_CG +#include <CoreGraphics/CGContext.h> +#elif WEBKIT_USING_SKIA +#include "skia/ext/platform_canvas.h" +#endif + +using WebKit::WebCanvas; +using WebKit::WebCursorInfo; +using WebKit::WebDragData; +using WebKit::WebDragOperationsMask; +using WebKit::WebFrame; +using WebKit::WebImage; +using WebKit::WebInputEvent; +using WebKit::WebPluginContainer; +using WebKit::WebPoint; +using WebKit::WebRect; +using WebKit::WebSize; +using WebKit::WebURLError; +using WebKit::WebURLRequest; +using WebKit::WebVector; +using WebKit::WebView; + +WebViewPlugin::WebViewPlugin(WebViewPlugin::Delegate* delegate) + : delegate_(delegate), + container_(NULL) { + web_view_ = WebView::create(this, NULL); + web_view_->initializeMainFrame(this); +} + +WebViewPlugin::~WebViewPlugin() { + web_view_->close(); +} + +bool WebViewPlugin::initialize(WebPluginContainer* container) { + container_ = container; + return true; +} + +void WebViewPlugin::destroy() { + delegate_->WillDestroyPlugin(); + delegate_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void WebViewPlugin::paint(WebCanvas* canvas, const WebRect& rect) { + gfx::Rect paintRect(rect_.Intersect(rect)); + if (paintRect.IsEmpty()) + return; + + paintRect.Offset(-rect_.x(), -rect_.y()); + +#if WEBKIT_USING_CG + CGContextRef context = canvas; + CGContextTranslateCTM(context, rect_.x(), rect_.y()); + CGContextSaveGState(context); +#elif WEBKIT_USING_SKIA + skia::PlatformCanvas* platform_canvas = canvas; + platform_canvas->translate(SkIntToScalar(rect_.x()), + SkIntToScalar(rect_.y())); + platform_canvas->save(); +#endif + + web_view_->layout(); + web_view_->paint(canvas, paintRect); + +#if WEBKIT_USING_SKIA + platform_canvas->restore(); +#elif WEBKIT_USING_CG + CGContextRestoreGState(context); +#endif +} + +// Coordinates are relative to the containing window. +void WebViewPlugin::updateGeometry( + const WebRect& frame_rect, const WebRect& clip_rect, + const WebVector<WebRect>& cut_out_rects, bool is_visible) { + if (frame_rect != rect_) { + rect_ = frame_rect; + web_view_->resize(WebSize(frame_rect.width, frame_rect.height)); + } +} + +bool WebViewPlugin::handleInputEvent(const WebInputEvent& event, + WebCursorInfo& cursor) { + current_cursor_ = cursor; + bool handled = web_view_->handleInputEvent(event); + cursor = current_cursor_; + return handled; +} + +void WebViewPlugin::startDragging(const WebDragData&, + WebDragOperationsMask, + const WebImage&, + const WebPoint&) { + // Immediately stop dragging. + web_view_->dragSourceSystemDragEnded(); +} + +void WebViewPlugin::didInvalidateRect(const WebRect& rect) { + if (container_) + container_->invalidateRect(WebRect(rect)); +} + +void WebViewPlugin::didChangeCursor(const WebCursorInfo& cursor) { + current_cursor_ = cursor; +} + +void WebViewPlugin::didClearWindowObject(WebFrame* frame) { + delegate_->BindWebFrame(frame); +} + +bool WebViewPlugin::canHandleRequest(WebFrame* frame, + const WebURLRequest& request) { + return GURL(request.url()).SchemeIs("chrome"); +} + +WebURLError WebViewPlugin::cancelledError(WebFrame* frame, + const WebURLRequest& request) { + // Return an error with a non-zero reason so isNull() on the corresponding + // ResourceError is false. + WebURLError error; + error.domain = "WebViewPlugin"; + error.reason = -1; + error.unreachableURL = request.url(); + return error; +} diff --git a/webkit/glue/plugins/webview_plugin.h b/webkit/glue/plugins/webview_plugin.h new file mode 100644 index 0000000..2e41218 --- /dev/null +++ b/webkit/glue/plugins/webview_plugin.h @@ -0,0 +1,107 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ +#define WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ + +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPlugin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrameClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebViewClient.h" + +// This class implements the WebPlugin interface by forwarding drawing and +// handling input events to a WebView. +// It can be used as a placeholder for an actual plugin, using HTML for the UI. +// To show HTML data inside the WebViewPlugin, +// call web_view->mainFrame()->loadHTMLString() with the HTML data and a fake +// chrome:// URL as origin. + +class WebViewPlugin: public WebKit::WebPlugin, public WebKit::WebViewClient, + public WebKit::WebFrameClient { + public: + class Delegate { + public: + // Bind |frame| to a Javascript object, enabling the delegate to receive + // callback methods from Javascript inside the WebFrame. + // This method is called from WebFrameClient::didClearWindowObject. + virtual void BindWebFrame(WebKit::WebFrame* frame) = 0; + + // Called before the WebViewPlugin is destroyed. The delegate should delete + // itself here. + virtual void WillDestroyPlugin() = 0; + }; + + explicit WebViewPlugin(Delegate* delegate); + + virtual WebKit::WebView* web_view() { return web_view_; } + + virtual WebKit::WebPluginContainer* container() { return container_; } + + // WebPlugin methods: + virtual bool initialize(WebKit::WebPluginContainer*); + virtual void destroy(); + + virtual NPObject* scriptableObject() { return NULL; } + + virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect); + + // Coordinates are relative to the containing window. + virtual void updateGeometry( + const WebKit::WebRect& frame_rect, const WebKit::WebRect& clip_rect, + const WebKit::WebVector<WebKit::WebRect>& cut_out_rects, bool is_visible); + + virtual void updateFocus(bool) { } + virtual void updateVisibility(bool) { } + + virtual bool acceptsInputEvents() { return true; } + virtual bool handleInputEvent(const WebKit::WebInputEvent& event, + WebKit::WebCursorInfo& cursor_info); + + virtual void didReceiveResponse(const WebKit::WebURLResponse& response) { } + virtual void didReceiveData(const char* data, int data_length) { } + virtual void didFinishLoading() { } + virtual void didFailLoading(const WebKit::WebURLError& error) { } + + // Called in response to WebPluginContainer::loadFrameRequest + virtual void didFinishLoadingFrameRequest( + const WebKit::WebURL& url, void* notifyData) { } + virtual void didFailLoadingFrameRequest(const WebKit::WebURL& url, + void* notify_data, + const WebKit::WebURLError& error) { } + + // WebViewClient methods: + virtual bool acceptsLoadDrops() { return false; } + + virtual void startDragging(const WebKit::WebDragData& drag_data, + WebKit::WebDragOperationsMask mask, + const WebKit::WebImage& image, + const WebKit::WebPoint& point); + + // WebWidgetClient methods: + virtual void didInvalidateRect(const WebKit::WebRect&); + virtual void didChangeCursor(const WebKit::WebCursorInfo& cursor); + + // WebFrameClient methods: + virtual void didClearWindowObject(WebKit::WebFrame* frame); + + virtual bool canHandleRequest(WebKit::WebFrame* frame, + const WebKit::WebURLRequest& request); + + virtual WebKit::WebURLError cancelledError( + WebKit::WebFrame* frame, const WebKit::WebURLRequest& request); + + private: + friend class DeleteTask<WebViewPlugin>; + virtual ~WebViewPlugin(); + + Delegate* delegate_; + WebKit::WebCursorInfo current_cursor_; + WebKit::WebPluginContainer* container_; + WebKit::WebView* web_view_; + gfx::Rect rect_; +}; + +#endif // WEBKIT_GLUE_PLUGINS_WEBVIEW_PLUGIN_H_ diff --git a/webkit/glue/regular_expression_unittest.cc b/webkit/glue/regular_expression_unittest.cc new file mode 100644 index 0000000..39741f8 --- /dev/null +++ b/webkit/glue/regular_expression_unittest.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 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 "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRegularExpression.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebTextCaseSensitivity.h" + +using namespace WebKit; + +namespace { + +class RegexTest : public testing::Test { +}; + +struct Match { + const WebUChar* text; + const int textLength; + const int matchPosition; + const int matchLength; +}; + +void testMatches(const WebRegularExpression& regex, + const Match* matches, + const size_t nMatches) { + + for (size_t i = 0; i < nMatches; ++i) { + int matchedLength = matches[i].textLength; + EXPECT_EQ(matches[i].matchPosition, regex.match( + WebString(matches[i].text, matches[i].textLength), 0, &matchedLength)); + if (matches[i].matchPosition != -1) + EXPECT_EQ(matches[i].matchLength, matchedLength); + } + +} + +} // namespace + +#define MATCH_DESC(webuchar, matchPosition, matchLength) \ + { webuchar, arraysize(webuchar), matchPosition, matchLength } + + +TEST(RegexTest, Basic) { + // Just make sure we're not completely broken. + WebRegularExpression regex("the quick brown fox", WebTextCaseSensitive); + EXPECT_EQ(0, regex.match("the quick brown fox")); + EXPECT_EQ(1, regex.match(" the quick brown fox")); + EXPECT_EQ(3, regex.match("foothe quick brown foxbar")); + + EXPECT_EQ(-1, regex.match("The quick brown FOX")); + EXPECT_EQ(-1, regex.match("the quick brown fo")); +} + +TEST(RegexTest, Unicode) { + // Make sure we get the right offsets for unicode strings. + WebUChar pattern[] = {L'\x6240', L'\x6709', L'\x7f51', L'\x9875'}; + WebRegularExpression regex(WebString(pattern, arraysize(pattern)), + WebTextCaseInsensitive); + + WebUChar text1[] = {L'\x6240', L'\x6709', L'\x7f51', L'\x9875'}; + WebUChar text2[] = {L' ', L'\x6240', L'\x6709', L'\x7f51', L'\x9875'}; + WebUChar text3[] = {L'f', L'o', L'o', L'\x6240', L'\x6709', L'\x7f51', L'\x9875', L'b', L'a', L'r'}; + WebUChar text4[] = {L'\x4e2d', L'\x6587', L'\x7f51', L'\x9875', L'\x6240', L'\x6709', L'\x7f51', L'\x9875'}; + + const Match matches[] = { + MATCH_DESC(text1, 0, 4), + MATCH_DESC(text2, 1, 4), + MATCH_DESC(text3, 3, 4), + MATCH_DESC(text4, 4, 4), + }; + + testMatches(regex, matches, arraysize(matches)); +} + +TEST(RegexTest, UnicodeMixedLength) { + WebUChar pattern[] = {L':', L'[', L' ', L'\x2000', L']', L'+', L':'}; + WebRegularExpression regex(WebString(pattern, arraysize(pattern)), + WebTextCaseInsensitive); + + WebUChar text1[] = {L':', L' ', L' ', L':'}; + WebUChar text2[] = {L' ', L' ', L':', L' ', L' ', L' ', L' ', L':', L' ', L' '}; + WebUChar text3[] = {L' ', L':', L' ', L'\x2000', L' ', L':', L' '}; + WebUChar text4[] = {L'\x6240', L'\x6709', L'\x7f51', L'\x9875', L' ', L':', L' ', L'\x2000', L' ', L'\x2000', L' ', L':', L' '}; + WebUChar text5[] = {L' '}; + WebUChar text6[] = {L':', L':'}; + + const Match matches[] = { + MATCH_DESC(text1, 0, 4), + MATCH_DESC(text2, 2, 6), + MATCH_DESC(text3, 1, 5), + MATCH_DESC(text4, 5, 7), + MATCH_DESC(text5, -1, -1), + MATCH_DESC(text6, -1, -1), + }; + + testMatches(regex, matches, arraysize(matches)); +} + +TEST(RegexTest, EmptyMatch) { + WebRegularExpression regex("|x", WebTextCaseInsensitive); + int matchedLength = 0; + EXPECT_EQ(0, regex.match("", 0, &matchedLength)); + EXPECT_EQ(0, matchedLength); +} diff --git a/webkit/glue/resource_fetcher.cc b/webkit/glue/resource_fetcher.cc new file mode 100644 index 0000000..186092b --- /dev/null +++ b/webkit/glue/resource_fetcher.cc @@ -0,0 +1,129 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/resource_fetcher.h" + +#include "base/logging.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" + +using base::TimeDelta; +using WebKit::WebFrame; +using WebKit::WebURLError; +using WebKit::WebURLLoader; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +ResourceFetcher::ResourceFetcher(const GURL& url, WebFrame* frame, + Callback* c) + : url_(url), + callback_(c), + completed_(false) { + // Can't do anything without a frame. However, delegate can be NULL (so we + // can do a http request and ignore the results). + DCHECK(frame); + Start(frame); +} + +ResourceFetcher::~ResourceFetcher() { + if (!completed_ && loader_.get()) + loader_->cancel(); +} + +void ResourceFetcher::Cancel() { + if (!completed_) { + loader_->cancel(); + completed_ = true; + } +} + +void ResourceFetcher::Start(WebFrame* frame) { + WebURLRequest request(url_); + frame->dispatchWillSendRequest(request); + + loader_.reset(WebKit::webKitClient()->createURLLoader()); + loader_->loadAsynchronously(request, this); +} + +///////////////////////////////////////////////////////////////////////////// +// WebURLLoaderClient methods + +void ResourceFetcher::willSendRequest( + WebURLLoader* loader, WebURLRequest& new_request, + const WebURLResponse& redirect_response) { +} + +void ResourceFetcher::didSendData( + WebURLLoader* loader, unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { +} + +void ResourceFetcher::didReceiveResponse( + WebURLLoader* loader, const WebURLResponse& response) { + DCHECK(!completed_); + response_ = response; +} + +void ResourceFetcher::didReceiveData( + WebURLLoader* loader, const char* data, int data_length) { + DCHECK(!completed_); + DCHECK(data_length > 0); + + data_.append(data, data_length); +} + +void ResourceFetcher::didReceiveCachedMetadata( + WebURLLoader* loader, const char* data, int data_length) { + DCHECK(!completed_); + DCHECK(data_length > 0); + + metadata_.assign(data, data_length); +} + +void ResourceFetcher::didFinishLoading(WebURLLoader* loader) { + DCHECK(!completed_); + completed_ = true; + + if (callback_.get()) { + callback_->Run(response_, data_); + callback_.reset(); + } +} + +void ResourceFetcher::didFail(WebURLLoader* loader, const WebURLError& error) { + DCHECK(!completed_); + completed_ = true; + + // Go ahead and tell our delegate that we're done. + if (callback_.get()) { + callback_->Run(WebURLResponse(), std::string()); + callback_.reset(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// A resource fetcher with a timeout + +ResourceFetcherWithTimeout::ResourceFetcherWithTimeout( + const GURL& url, WebFrame* frame, int timeout_secs, Callback* c) + : ResourceFetcher(url, frame, c) { + timeout_timer_.Start(TimeDelta::FromSeconds(timeout_secs), this, + &ResourceFetcherWithTimeout::TimeoutFired); +} + +void ResourceFetcherWithTimeout::TimeoutFired() { + if (!completed_) { + loader_->cancel(); + didFail(NULL, WebURLError()); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/resource_fetcher.h b/webkit/glue/resource_fetcher.h new file mode 100644 index 0000000..7910fc1 --- /dev/null +++ b/webkit/glue/resource_fetcher.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 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. +// +// A wrapper around ResourceHandle and ResourceHandleClient that simplifies +// the download of an HTTP object. The interface is modeled after URLFetcher +// in the /chrome/browser. +// +// ResourceFetcher::Delegate::OnURLFetchComplete will be called async after +// the ResourceFetcher object is created. + +#ifndef WEBKIT_GLUE_RESOURCE_FETCHER_H_ +#define WEBKIT_GLUE_RESOURCE_FETCHER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +class GURL; + +namespace WebKit { +class WebFrame; +class WebURLLoader; +class WebURLRequest; +struct WebURLError; +} + +namespace webkit_glue { + +class ResourceFetcher : public WebKit::WebURLLoaderClient { + public: + // This will be called when the URL has been fetched, successfully or not. + // If there is a failure, response and data will both be empty. |response| + // and |data| are both valid until the URLFetcher instance is destroyed. + typedef Callback2<const WebKit::WebURLResponse&, + const std::string&>::Type Callback; + + // We need a frame to make requests. + ResourceFetcher( + const GURL& url, WebKit::WebFrame* frame, Callback* callback); + ~ResourceFetcher(); + + // Stop the request and don't call the callback. + void Cancel(); + + bool completed() const { return completed_; } + + protected: + // WebURLLoaderClient methods: + virtual void willSendRequest( + WebKit::WebURLLoader* loader, WebKit::WebURLRequest& new_request, + const WebKit::WebURLResponse& redirect_response); + virtual void didSendData( + WebKit::WebURLLoader* loader, unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent); + virtual void didReceiveResponse( + WebKit::WebURLLoader* loader, const WebKit::WebURLResponse& response); + virtual void didReceiveCachedMetadata( + WebKit::WebURLLoader* loader, const char* data, int data_length); + virtual void didReceiveData( + WebKit::WebURLLoader* loader, const char* data, int data_length); + virtual void didFinishLoading(WebKit::WebURLLoader* loader); + virtual void didFail( + WebKit::WebURLLoader* loader, const WebKit::WebURLError& error); + + scoped_ptr<WebKit::WebURLLoader> loader_; + + // URL we're fetching + GURL url_; + + // Callback when we're done + scoped_ptr<Callback> callback_; + + // A copy of the original resource response + WebKit::WebURLResponse response_; + + // Set to true once the request is compelte. + bool completed_; + + private: + // Start the actual download. + void Start(WebKit::WebFrame* frame); + + // Buffer to hold the content from the server. + std::string data_; + + // Buffer to hold metadata from the cache. + std::string metadata_; +}; + +///////////////////////////////////////////////////////////////////////////// +// A resource fetcher with a timeout +class ResourceFetcherWithTimeout : public ResourceFetcher { + public: + ResourceFetcherWithTimeout(const GURL& url, WebKit::WebFrame* frame, + int timeout_secs, Callback* c); + virtual ~ResourceFetcherWithTimeout() {} + + private: + // Callback for timer that limits how long we wait for the alternate error + // page server. If this timer fires and the request hasn't completed, we + // kill the request. + void TimeoutFired(); + + // Limit how long we wait for the alternate error page server. + base::OneShotTimer<ResourceFetcherWithTimeout> timeout_timer_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_RESOURCE_FETCHER_H_ diff --git a/webkit/glue/resource_fetcher_unittest.cc b/webkit/glue/resource_fetcher_unittest.cc new file mode 100644 index 0000000..98cef0a --- /dev/null +++ b/webkit/glue/resource_fetcher_unittest.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/resource_fetcher.h" + +#include "base/callback.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/unittest_test_server.h" +#include "webkit/tools/test_shell/simple_resource_loader_bridge.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +#if defined(TOOLKIT_USES_GTK) +#include <gtk/gtk.h> +#endif + +using WebKit::WebFrame; +using WebKit::WebURLResponse; +using webkit_glue::ResourceFetcher; +using webkit_glue::ResourceFetcherWithTimeout; + +namespace { + +class ResourceFetcherTests : public TestShellTest { + public: + void SetUp() { + TestShellTest::SetUp(); + } + void TearDown() { + TestShellTest::TearDown(); + } +}; + +static const int kMaxWaitTimeMs = 5000; +static const int kWaitIntervalMs = 100; + +class FetcherDelegate { + public: + FetcherDelegate() + : timer_id_(0), completed_(false), time_elapsed_ms_(0) { + // Start a repeating timer waiting for the download to complete. The + // callback has to be a static function, so we hold on to our instance. + FetcherDelegate::instance_ = this; + CreateTimer(kWaitIntervalMs); + } + + ResourceFetcher::Callback* NewCallback() { + return ::NewCallback(this, &FetcherDelegate::OnURLFetchComplete); + } + + void OnURLFetchComplete(const WebURLResponse& response, + const std::string& data) { + response_ = response; + data_ = data; + completed_ = true; + DestroyTimer(); + MessageLoop::current()->Quit(); + } + + bool completed() const { return completed_; } + bool timed_out() const { return time_elapsed_ms_ > kMaxWaitTimeMs; } + + int time_elapsed_ms() const { return time_elapsed_ms_; } + std::string data() const { return data_; } + const WebURLResponse& response() const { return response_; } + + // Wait for the request to complete or timeout. We use a loop here b/c the + // testing infrastructure (test_shell) can generate spurious calls to the + // MessageLoop's Quit method. + void WaitForResponse() { + while (!completed() && !timed_out()) + MessageLoop::current()->Run(); + } + + void CreateTimer(int interval) { +#if defined(OS_WIN) + timer_id_ = ::SetTimer(NULL, NULL, interval, + &FetcherDelegate::TimerCallback); +#elif defined(TOOLKIT_USES_GTK) + timer_id_ = g_timeout_add(interval, &FetcherDelegate::TimerCallback, NULL); +#elif defined(OS_MACOSX) + // CFAbsoluteTime is in seconds and |interval| is in ms, so make sure we + // keep the units correct. + CFTimeInterval interval_in_seconds = static_cast<double>(interval) / 1000.0; + CFAbsoluteTime fire_date = + CFAbsoluteTimeGetCurrent() + interval_in_seconds; + timer_id_ = CFRunLoopTimerCreate(NULL, fire_date, interval_in_seconds, 0, + 0, FetcherDelegate::TimerCallback, NULL); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer_id_, kCFRunLoopCommonModes); +#endif + } + + void DestroyTimer() { +#if defined(OS_WIN) + ::KillTimer(NULL, timer_id_); +#elif defined(TOOLKIT_USES_GTK) + g_source_remove(timer_id_); +#elif defined(OS_MACOSX) + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer_id_, + kCFRunLoopCommonModes); + CFRelease(timer_id_); +#endif + } + +#if defined(OS_WIN) + // Static timer callback, just passes through to instance version. + static VOID CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR timer_id, + DWORD ms) { + instance_->TimerFired(); + } +#elif defined(TOOLKIT_USES_GTK) + static gboolean TimerCallback(gpointer data) { + instance_->TimerFired(); + return true; + } +#elif defined(OS_MACOSX) + static void TimerCallback(CFRunLoopTimerRef timer, void* info) { + instance_->TimerFired(); + } +#endif + + void TimerFired() { + ASSERT_FALSE(completed_); + + if (timed_out()) { + DestroyTimer(); + MessageLoop::current()->Quit(); + FAIL() << "fetch timed out"; + return; + } + + time_elapsed_ms_ += kWaitIntervalMs; + } + + static FetcherDelegate* instance_; + + private: +#if defined(OS_WIN) + UINT_PTR timer_id_; +#elif defined(TOOLKIT_USES_GTK) + guint timer_id_; +#elif defined(OS_MACOSX) + CFRunLoopTimerRef timer_id_; +#endif + bool completed_; + int time_elapsed_ms_; + WebURLResponse response_; + std::string data_; +}; + +FetcherDelegate* FetcherDelegate::instance_ = NULL; + +// Test a fetch from the test server. +TEST_F(ResourceFetcherTests, ResourceFetcherDownload) { + scoped_refptr<UnittestTestServer> server = + UnittestTestServer::CreateServer(); + ASSERT_TRUE(NULL != server.get()); + + WebFrame* frame = test_shell_->webView()->mainFrame(); + + GURL url = server->TestServerPage("files/test_shell/index.html"); + scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); + scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcher( + url, frame, delegate->NewCallback())); + + delegate->WaitForResponse(); + + ASSERT_TRUE(delegate->completed()); + EXPECT_EQ(delegate->response().httpStatusCode(), 200); + std::string text = delegate->data(); + EXPECT_TRUE(text.find("What is this page?") != std::string::npos); + + // Test 404 response. + url = server->TestServerPage("files/thisfiledoesntexist.html"); + delegate.reset(new FetcherDelegate); + fetcher.reset(new ResourceFetcher(url, frame, delegate->NewCallback())); + + delegate->WaitForResponse(); + + ASSERT_TRUE(delegate->completed()); + EXPECT_EQ(delegate->response().httpStatusCode(), 404); + EXPECT_TRUE(delegate->data().find("Not Found.") != std::string::npos); +} + +TEST_F(ResourceFetcherTests, ResourceFetcherDidFail) { + scoped_refptr<UnittestTestServer> server = + UnittestTestServer::CreateServer(); + ASSERT_TRUE(NULL != server.get()); + + WebFrame* frame = test_shell_->webView()->mainFrame(); + + // Try to fetch a page on a site that doesn't exist. + GURL url("http://localhost:1339/doesnotexist"); + scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); + scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcher( + url, frame, delegate->NewCallback())); + + delegate->WaitForResponse(); + + // When we fail, we still call the Delegate callback but we pass in empty + // values. + EXPECT_TRUE(delegate->completed()); + EXPECT_TRUE(delegate->response().isNull()); + EXPECT_EQ(delegate->data(), std::string()); + EXPECT_TRUE(delegate->time_elapsed_ms() < kMaxWaitTimeMs); +} + +TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) { + scoped_refptr<UnittestTestServer> server = + UnittestTestServer::CreateServer(); + ASSERT_TRUE(NULL != server.get()); + + WebFrame* frame = test_shell_->webView()->mainFrame(); + + // Grab a page that takes at least 1 sec to respond, but set the fetcher to + // timeout in 0 sec. + GURL url = server->TestServerPage("slow?1"); + scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); + scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcherWithTimeout( + url, frame, 0, delegate->NewCallback())); + + delegate->WaitForResponse(); + + // When we timeout, we still call the Delegate callback but we pass in empty + // values. + EXPECT_TRUE(delegate->completed()); + EXPECT_TRUE(delegate->response().isNull()); + EXPECT_EQ(delegate->data(), std::string()); + EXPECT_TRUE(delegate->time_elapsed_ms() < kMaxWaitTimeMs); +} + +} // namespace diff --git a/webkit/glue/resource_loader_bridge.cc b/webkit/glue/resource_loader_bridge.cc new file mode 100644 index 0000000..8845256 --- /dev/null +++ b/webkit/glue/resource_loader_bridge.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/resource_loader_bridge.h" + +#include "webkit/appcache/appcache_interfaces.h" +#include "net/http/http_response_headers.h" + +namespace webkit_glue { + +ResourceLoaderBridge::RequestInfo::RequestInfo() + : load_flags(0), + requestor_pid(0), + request_type(ResourceType::MAIN_FRAME), + request_context(0), + appcache_host_id(0), + routing_id(0) { +} + +ResourceLoaderBridge::RequestInfo::~RequestInfo() { +} + +ResourceLoaderBridge::LoadTimingInfo::LoadTimingInfo() { + proxy_start = -1; + proxy_end = -1; + dns_start = -1; + dns_end = -1; + connect_start = -1; + connect_end = -1; + ssl_start = -1; + ssl_end = -1; + send_start = 0; + send_end = 0; + receive_headers_start = 0; + receive_headers_end = 0; +} + +ResourceLoaderBridge::LoadTimingInfo::~LoadTimingInfo() { +} + +ResourceLoaderBridge::ResponseInfo::ResponseInfo() { + content_length = -1; + appcache_id = appcache::kNoCacheId; + was_fetched_via_spdy = false; + was_npn_negotiated = false; + connection_id = 0; + connection_reused = false; + was_alternate_protocol_available = false; + was_fetched_via_proxy = false; +} + +ResourceLoaderBridge::ResponseInfo::~ResponseInfo() { +} + +ResourceLoaderBridge::SyncLoadResponse::SyncLoadResponse() { +} + +ResourceLoaderBridge::SyncLoadResponse::~SyncLoadResponse() { +} + +ResourceLoaderBridge::ResourceLoaderBridge() { +} + +ResourceLoaderBridge::~ResourceLoaderBridge() { +} + +} // namespace webkit_glue diff --git a/webkit/glue/resource_loader_bridge.h b/webkit/glue/resource_loader_bridge.h new file mode 100644 index 0000000..e66181f --- /dev/null +++ b/webkit/glue/resource_loader_bridge.h @@ -0,0 +1,345 @@ +// Copyright (c) 2006-2008 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. +// +// The intent of this file is to provide a type-neutral abstraction between +// Chrome and WebKit for resource loading. This pure-virtual interface is +// implemented by the embedder, which also provides a factory method Create +// to instantiate this object. +// +// One of these objects will be created by WebKit for each request. WebKit +// will own the pointer to the bridge, and will delete it when the request is +// no longer needed. +// +// In turn, the bridge's owner on the WebKit end will implement the Peer +// interface, which we will use to communicate notifications back. + +#ifndef WEBKIT_GLUE_RESOURCE_LOADER_BRIDGE_H_ +#define WEBKIT_GLUE_RESOURCE_LOADER_BRIDGE_H_ + +#include "build/build_config.h" +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif +#include "base/platform_file.h" +#include "base/ref_counted.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request_status.h" +#include "webkit/glue/resource_type.h" + +namespace net { +class HttpResponseHeaders; +} + +class FilePath; + +namespace webkit_glue { + +class ResourceLoaderBridge { + public: + // Structure used when calling ResourceLoaderBridge::Create(). + struct RequestInfo { + RequestInfo(); + ~RequestInfo(); + + // HTTP-style method name (e.g., "GET" or "POST"). + std::string method; + + // Absolute URL encoded in ASCII per the rules of RFC-2396. + GURL url; + + // URL of the document in the top-level window, which may be checked by the + // third-party cookie blocking policy. + GURL first_party_for_cookies; + + // Optional parameter, a URL with similar constraints in how it must be + // encoded as the url member. + GURL referrer; + + std::string frame_origin; + std::string main_frame_origin; + + // For HTTP(S) requests, the headers parameter can be a \r\n-delimited and + // \r\n-terminated list of MIME headers. They should be ASCII-encoded using + // the standard MIME header encoding rules. The headers parameter can also + // be null if no extra request headers need to be set. + std::string headers; + + // Composed of the values defined in url_request_load_flags.h. + int load_flags; + + // Process id of the process making the request. + int requestor_pid; + + // Indicates if the current request is the main frame load, a sub-frame + // load, or a sub objects load. + ResourceType::Type request_type; + + // Used for plugin to browser requests. + uint32 request_context; + + // Identifies what appcache host this request is associated with. + int appcache_host_id; + + // Used to associated the bridge with a frame's network context. + int routing_id; + }; + + // Structure containing timing information for the request. It addresses + // http://groups.google.com/group/http-archive-specification/web/har-1-1-spec + // and http://dev.w3.org/2006/webapi/WebTiming/ needs. + // + // All the values for starts and ends are given in milliseconds and are + // offsets with respect to the given base time. + struct LoadTimingInfo { + LoadTimingInfo(); + ~LoadTimingInfo(); + + // All the values in this struct are given as offsets in milliseconds wrt + // this base time. + base::Time base_time; + + // The time that proxy processing started. For requests with no proxy phase, + // this time is -1. + int32 proxy_start; + + // The time that proxy processing ended. For reused sockets this time + // is -1. + int32 proxy_end; + + // The time that DNS lookup started. For reused sockets this time is -1. + int32 dns_start; + + // The time that DNS lookup ended. For reused sockets this time is -1. + int32 dns_end; + + // The time that establishing connection started. For reused sockets + // this time is -1. Connect time includes dns time. + int32 connect_start; + + // The time that establishing connection ended. For reused sockets this + // time is -1. Connect time includes dns time. + int32 connect_end; + + // The time at which SSL handshake started. For non-HTTPS requests this + // is 0. + int32 ssl_start; + + // The time at which SSL handshake ended. For non-HTTPS requests this is 0. + int32 ssl_end; + + // The time that HTTP request started. For non-HTTP requests this is 0. + int32 send_start; + + // The time that HTTP request ended. For non-HTTP requests this is 0. + int32 send_end; + + // The time at which receiving HTTP headers started. For non-HTTP requests + // this is 0. + int32 receive_headers_start; + + // The time at which receiving HTTP headers ended. For non-HTTP requests + // this is 0. + int32 receive_headers_end; + }; + + struct ResponseInfo { + ResponseInfo(); + ~ResponseInfo(); + + // The time at which the request was made that resulted in this response. + // For cached responses, this time could be "far" in the past. + base::Time request_time; + + // The time at which the response headers were received. For cached + // responses, this time could be "far" in the past. + base::Time response_time; + + // The response headers or NULL if the URL type does not support headers. + scoped_refptr<net::HttpResponseHeaders> headers; + + // The mime type of the response. This may be a derived value. + std::string mime_type; + + // The character encoding of the response or none if not applicable to the + // response's mime type. This may be a derived value. + std::string charset; + + // An opaque string carrying security information pertaining to this + // response. This may include information about the SSL connection used. + std::string security_info; + + // Content length if available. -1 if not available + int64 content_length; + + // The appcache this response was loaded from, or kNoCacheId. + int64 appcache_id; + + // The manifest url of the appcache this response was loaded from. + // Note: this value is only populated for main resource requests. + GURL appcache_manifest_url; + + // Connection identifier from the underlying network stack. In case there + // is no associated connection, contains 0. + uint32 connection_id; + + // Determines whether physical connection reused. + bool connection_reused; + + // Detailed timing information used by the WebTiming, HAR and Developer + // Tools. + LoadTimingInfo load_timing; + + // True if the response was delivered using SPDY. + bool was_fetched_via_spdy; + + // True if the response was delivered after NPN is negotiated. + bool was_npn_negotiated; + + // True if response could use alternate protocol. However, browser will + // ignore the alternate protocol when spdy is not enabled on browser side. + bool was_alternate_protocol_available; + + // True if the response was fetched via an explicit proxy (as opposed to a + // transparent proxy). The proxy could be any type of proxy, HTTP or SOCKS. + // Note: we cannot tell if a transparent proxy may have been involved. + bool was_fetched_via_proxy; + }; + + // See the SyncLoad method declared below. (The name of this struct is not + // suffixed with "Info" because it also contains the response data.) + struct SyncLoadResponse : ResponseInfo { + SyncLoadResponse(); + ~SyncLoadResponse(); + + // The response status. + URLRequestStatus status; + + // The final URL of the response. This may differ from the request URL in + // the case of a server redirect. + GURL url; + + // The response data. + std::string data; + }; + + // Generated by the bridge. This is implemented by our custom resource loader + // within webkit. The Peer and it's bridge should have identical lifetimes + // as they represent each end of a communication channel. + // + // These callbacks mirror URLRequest::Delegate and the order and conditions + // in which they will be called are identical. See url_request.h for more + // information. + class Peer { + public: + virtual ~Peer() {} + + // Called as upload progress is made. + // note: only for requests with LOAD_ENABLE_UPLOAD_PROGRESS set + virtual void OnUploadProgress(uint64 position, uint64 size) = 0; + + // Called when a redirect occurs. The implementation may return false to + // suppress the redirect. The given ResponseInfo provides complete + // information about the redirect, and new_url is the URL that will be + // loaded if this method returns true. If this method returns true, the + // output parameter *has_new_first_party_for_cookies indicates whether the + // output parameter *new_first_party_for_cookies contains the new URL that + // should be consulted for the third-party cookie blocking policy. + virtual bool OnReceivedRedirect(const GURL& new_url, + const ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) = 0; + + // Called when response headers are available (after all redirects have + // been followed). |content_filtered| is set to true if the contents is + // altered or replaced (usually for security reasons when the resource is + // deemed unsafe). + virtual void OnReceivedResponse(const ResponseInfo& info, + bool content_filtered) = 0; + + // Called when a chunk of response data is available. This method may + // be called multiple times or not at all if an error occurs. + virtual void OnReceivedData(const char* data, int len) = 0; + + // Called when metadata generated by the renderer is retrieved from the + // cache. This method may be called zero or one times. + virtual void OnReceivedCachedMetadata(const char* data, int len) { } + + // Called when the response is complete. This method signals completion of + // the resource load.ff + virtual void OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info) = 0; + + // Returns the URL of the request, which allows us to display it in + // debugging situations. + virtual GURL GetURLForDebugging() const = 0; + }; + + // use Create() for construction, but anybody can delete at any time, + // INCLUDING during processing of callbacks. + virtual ~ResourceLoaderBridge(); + + // Call this method to make a new instance. + // + // For HTTP(S) POST requests, the AppendDataToUpload and AppendFileToUpload + // methods may be called to construct the body of the request. + static ResourceLoaderBridge* Create(const RequestInfo& request_info); + + // Call this method before calling Start() to append a chunk of binary data + // to the request body. May only be used with HTTP(S) POST requests. + virtual void AppendDataToUpload(const char* data, int data_len) = 0; + + // Call this method before calling Start() to append the contents of a file + // to the request body. May only be used with HTTP(S) POST requests. + void AppendFileToUpload(const FilePath& file_path) { + AppendFileRangeToUpload(file_path, 0, kuint64max, base::Time()); + } + + // Call this method before calling Start() to append the contents of a file + // to the request body. May only be used with HTTP(S) POST requests. + virtual void AppendFileRangeToUpload( + const FilePath& file_path, + uint64 offset, + uint64 length, + const base::Time& expected_modification_time) = 0; + + // Call this method before calling Start() to assign an upload identifier to + // this request. This is used to enable caching of POST responses. A value + // of 0 implies the unspecified identifier. + virtual void SetUploadIdentifier(int64 identifier) = 0; + + // Call this method to initiate the request. If this method succeeds, then + // the peer's methods will be called asynchronously to report various events. + virtual bool Start(Peer* peer) = 0; + + // Call this method to cancel a request that is in progress. This method + // causes the request to immediately transition into the 'done' state. The + // OnCompletedRequest method will be called asynchronously; this assumes + // the peer is still valid. + virtual void Cancel() = 0; + + // Call this method to suspend or resume a load that is in progress. This + // method may only be called after a successful call to the Start method. + virtual void SetDefersLoading(bool value) = 0; + + // Call this method to load the resource synchronously (i.e., in one shot). + // This is an alternative to the Start method. Be warned that this method + // will block the calling thread until the resource is fully downloaded or an + // error occurs. It could block the calling thread for a long time, so only + // use this if you really need it! There is also no way for the caller to + // interrupt this method. Errors are reported via the status field of the + // response parameter. + virtual void SyncLoad(SyncLoadResponse* response) = 0; + + protected: + // construction must go through Create() + ResourceLoaderBridge(); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceLoaderBridge); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_RESOURCE_LOADER_BRIDGE_H_ diff --git a/webkit/glue/resource_type.h b/webkit/glue/resource_type.h new file mode 100644 index 0000000..aa4a072 --- /dev/null +++ b/webkit/glue/resource_type.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_RESOURCE_TYPE_H__ +#define WEBKIT_GLUE_RESOURCE_TYPE_H__ + +#include "base/basictypes.h" + +class ResourceType { + public: + enum Type { + MAIN_FRAME = 0, // top level page + SUB_FRAME, // frame or iframe + STYLESHEET, // a CSS stylesheet + SCRIPT, // an external script + IMAGE, // an image (jpg/gif/png/etc) + FONT_RESOURCE, // a font + SUB_RESOURCE, // an "other" subresource. + OBJECT, // an object (or embed) tag for a plugin, + // or a resource that a plugin requested. + MEDIA, // a media resource. + WORKER, // the main resource of a dedicated worker. + SHARED_WORKER, // the main resource of a shared worker. + LAST_TYPE // Place holder so we don't need to change ValidType + // everytime. + }; + + static bool ValidType(int32 type) { + return type >= MAIN_FRAME && type < LAST_TYPE; + } + + static Type FromInt(int32 type) { + return static_cast<Type>(type); + } + + static bool IsFrame(ResourceType::Type type) { + return type == MAIN_FRAME || type == SUB_FRAME; + } + + static bool IsSharedWorker(ResourceType::Type type) { + return type == SHARED_WORKER; + } + + static bool IsSubresource(ResourceType::Type type) { + return type == STYLESHEET || + type == SCRIPT || + type == IMAGE || + type == FONT_RESOURCE || + type == SUB_RESOURCE || + type == WORKER; + } + + private: + // Don't instantiate this class. + ResourceType(); + ~ResourceType(); +}; +#endif // WEBKIT_GLUE_RESOURCE_TYPE_H__ diff --git a/webkit/glue/resources/README.txt b/webkit/glue/resources/README.txt new file mode 100644 index 0000000..c5aaa7a --- /dev/null +++ b/webkit/glue/resources/README.txt @@ -0,0 +1,97 @@ +The files listed below and found in this directory are copied from the +Mozilla source tree, where they are licensed under the MPL/GPL/LGPL. While +binary files do not contain a license block, I have pasted the relevant +Mozilla license block below. + +aliasb.cur +broken-image.gif +cell.cur +col_resize.cur +copy.cur +grab.cur +grabbing.cur +row_resize.cur +vertical_text.cur +zoom_in.cur +zoom_out.cur + +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +///////////////////////////////////////////////////////////////////////////// + +The files listed below and found in this directory are copied from the WebKit +source tree or are derivative works of files from the WebKit source tree. +They are licensed under a BSD license. While binary files do not contain +a license block, I have pasted the relevant WebKit license block below. + +pan_icon.png +pan_east.cur +pan_middle.cur +pan_north.cur +pan_north_east.cur +pan_north_west.cur +pan_south.cur +pan_south_east.cur +pan_south_west.cur +pan_west.cur +textarea_resize_corner.png + +// ***** BEGIN LICENSE BLOCK ***** + +Copyright (C) 2005 Apple Computer, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/webkit/glue/resources/aliasb.cur b/webkit/glue/resources/aliasb.cur Binary files differnew file mode 100644 index 0000000..8d9ac94 --- /dev/null +++ b/webkit/glue/resources/aliasb.cur diff --git a/webkit/glue/resources/broken-image.gif b/webkit/glue/resources/broken-image.gif Binary files differnew file mode 100644 index 0000000..735e10c --- /dev/null +++ b/webkit/glue/resources/broken-image.gif diff --git a/webkit/glue/resources/cell.cur b/webkit/glue/resources/cell.cur Binary files differnew file mode 100644 index 0000000..decfbdc --- /dev/null +++ b/webkit/glue/resources/cell.cur diff --git a/webkit/glue/resources/col_resize.cur b/webkit/glue/resources/col_resize.cur Binary files differnew file mode 100644 index 0000000..8f7f675 --- /dev/null +++ b/webkit/glue/resources/col_resize.cur diff --git a/webkit/glue/resources/copy.cur b/webkit/glue/resources/copy.cur Binary files differnew file mode 100644 index 0000000..87f1519 --- /dev/null +++ b/webkit/glue/resources/copy.cur diff --git a/webkit/glue/resources/dash.png b/webkit/glue/resources/dash.png Binary files differnew file mode 100644 index 0000000..1747604 --- /dev/null +++ b/webkit/glue/resources/dash.png diff --git a/webkit/glue/resources/linux-checkbox-disabled-indeterminate.png b/webkit/glue/resources/linux-checkbox-disabled-indeterminate.png Binary files differnew file mode 100644 index 0000000..171d001 --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-disabled-indeterminate.png diff --git a/webkit/glue/resources/linux-checkbox-disabled-off.png b/webkit/glue/resources/linux-checkbox-disabled-off.png Binary files differnew file mode 100644 index 0000000..c916674 --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-disabled-off.png diff --git a/webkit/glue/resources/linux-checkbox-disabled-on.png b/webkit/glue/resources/linux-checkbox-disabled-on.png Binary files differnew file mode 100644 index 0000000..d9ff1c6 --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-disabled-on.png diff --git a/webkit/glue/resources/linux-checkbox-indeterminate.png b/webkit/glue/resources/linux-checkbox-indeterminate.png Binary files differnew file mode 100644 index 0000000..dcde3c1 --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-indeterminate.png diff --git a/webkit/glue/resources/linux-checkbox-off.png b/webkit/glue/resources/linux-checkbox-off.png Binary files differnew file mode 100644 index 0000000..15cc949 --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-off.png diff --git a/webkit/glue/resources/linux-checkbox-on.png b/webkit/glue/resources/linux-checkbox-on.png Binary files differnew file mode 100644 index 0000000..4c72e8a --- /dev/null +++ b/webkit/glue/resources/linux-checkbox-on.png diff --git a/webkit/glue/resources/linux-progress-bar.png b/webkit/glue/resources/linux-progress-bar.png Binary files differnew file mode 100644 index 0000000..82a20f5 --- /dev/null +++ b/webkit/glue/resources/linux-progress-bar.png diff --git a/webkit/glue/resources/linux-progress-border-left.png b/webkit/glue/resources/linux-progress-border-left.png Binary files differnew file mode 100644 index 0000000..0dd0e50 --- /dev/null +++ b/webkit/glue/resources/linux-progress-border-left.png diff --git a/webkit/glue/resources/linux-progress-border-right.png b/webkit/glue/resources/linux-progress-border-right.png Binary files differnew file mode 100644 index 0000000..d351b11 --- /dev/null +++ b/webkit/glue/resources/linux-progress-border-right.png diff --git a/webkit/glue/resources/linux-progress-value.png b/webkit/glue/resources/linux-progress-value.png Binary files differnew file mode 100644 index 0000000..251917b --- /dev/null +++ b/webkit/glue/resources/linux-progress-value.png diff --git a/webkit/glue/resources/linux-radio-disabled-off.png b/webkit/glue/resources/linux-radio-disabled-off.png Binary files differnew file mode 100644 index 0000000..97aa6f9 --- /dev/null +++ b/webkit/glue/resources/linux-radio-disabled-off.png diff --git a/webkit/glue/resources/linux-radio-disabled-on.png b/webkit/glue/resources/linux-radio-disabled-on.png Binary files differnew file mode 100644 index 0000000..07aed36 --- /dev/null +++ b/webkit/glue/resources/linux-radio-disabled-on.png diff --git a/webkit/glue/resources/linux-radio-off.png b/webkit/glue/resources/linux-radio-off.png Binary files differnew file mode 100644 index 0000000..6a60a70 --- /dev/null +++ b/webkit/glue/resources/linux-radio-off.png diff --git a/webkit/glue/resources/linux-radio-on.png b/webkit/glue/resources/linux-radio-on.png Binary files differnew file mode 100644 index 0000000..cb47411 --- /dev/null +++ b/webkit/glue/resources/linux-radio-on.png diff --git a/webkit/glue/resources/media_pause.png b/webkit/glue/resources/media_pause.png Binary files differnew file mode 100644 index 0000000..5334d7e --- /dev/null +++ b/webkit/glue/resources/media_pause.png diff --git a/webkit/glue/resources/media_play.png b/webkit/glue/resources/media_play.png Binary files differnew file mode 100644 index 0000000..0027929 --- /dev/null +++ b/webkit/glue/resources/media_play.png diff --git a/webkit/glue/resources/media_play_disabled.png b/webkit/glue/resources/media_play_disabled.png Binary files differnew file mode 100644 index 0000000..cd9a4a5 --- /dev/null +++ b/webkit/glue/resources/media_play_disabled.png diff --git a/webkit/glue/resources/media_slider_thumb.png b/webkit/glue/resources/media_slider_thumb.png Binary files differnew file mode 100644 index 0000000..21909ce --- /dev/null +++ b/webkit/glue/resources/media_slider_thumb.png diff --git a/webkit/glue/resources/media_sound_disabled.png b/webkit/glue/resources/media_sound_disabled.png Binary files differnew file mode 100644 index 0000000..d7b2c1c --- /dev/null +++ b/webkit/glue/resources/media_sound_disabled.png diff --git a/webkit/glue/resources/media_sound_full.png b/webkit/glue/resources/media_sound_full.png Binary files differnew file mode 100644 index 0000000..2ecc142 --- /dev/null +++ b/webkit/glue/resources/media_sound_full.png diff --git a/webkit/glue/resources/media_sound_none.png b/webkit/glue/resources/media_sound_none.png Binary files differnew file mode 100644 index 0000000..378a005 --- /dev/null +++ b/webkit/glue/resources/media_sound_none.png diff --git a/webkit/glue/resources/media_volume_slider_thumb.png b/webkit/glue/resources/media_volume_slider_thumb.png Binary files differnew file mode 100644 index 0000000..4789937 --- /dev/null +++ b/webkit/glue/resources/media_volume_slider_thumb.png diff --git a/webkit/glue/resources/pan_east.cur b/webkit/glue/resources/pan_east.cur Binary files differnew file mode 100644 index 0000000..f7c0df3 --- /dev/null +++ b/webkit/glue/resources/pan_east.cur diff --git a/webkit/glue/resources/pan_icon.png b/webkit/glue/resources/pan_icon.png Binary files differnew file mode 100644 index 0000000..db4e372 --- /dev/null +++ b/webkit/glue/resources/pan_icon.png diff --git a/webkit/glue/resources/pan_middle.cur b/webkit/glue/resources/pan_middle.cur Binary files differnew file mode 100644 index 0000000..52ae366 --- /dev/null +++ b/webkit/glue/resources/pan_middle.cur diff --git a/webkit/glue/resources/pan_north.cur b/webkit/glue/resources/pan_north.cur Binary files differnew file mode 100644 index 0000000..0c3a4ee --- /dev/null +++ b/webkit/glue/resources/pan_north.cur diff --git a/webkit/glue/resources/pan_north_east.cur b/webkit/glue/resources/pan_north_east.cur Binary files differnew file mode 100644 index 0000000..4388fb8 --- /dev/null +++ b/webkit/glue/resources/pan_north_east.cur diff --git a/webkit/glue/resources/pan_north_west.cur b/webkit/glue/resources/pan_north_west.cur Binary files differnew file mode 100644 index 0000000..d47089e --- /dev/null +++ b/webkit/glue/resources/pan_north_west.cur diff --git a/webkit/glue/resources/pan_south.cur b/webkit/glue/resources/pan_south.cur Binary files differnew file mode 100644 index 0000000..f5d813b --- /dev/null +++ b/webkit/glue/resources/pan_south.cur diff --git a/webkit/glue/resources/pan_south_east.cur b/webkit/glue/resources/pan_south_east.cur Binary files differnew file mode 100644 index 0000000..ba9742e --- /dev/null +++ b/webkit/glue/resources/pan_south_east.cur diff --git a/webkit/glue/resources/pan_south_west.cur b/webkit/glue/resources/pan_south_west.cur Binary files differnew file mode 100644 index 0000000..4f62984 --- /dev/null +++ b/webkit/glue/resources/pan_south_west.cur diff --git a/webkit/glue/resources/pan_west.cur b/webkit/glue/resources/pan_west.cur Binary files differnew file mode 100644 index 0000000..9068925 --- /dev/null +++ b/webkit/glue/resources/pan_west.cur diff --git a/webkit/glue/resources/row_resize.cur b/webkit/glue/resources/row_resize.cur Binary files differnew file mode 100644 index 0000000..a7369d3 --- /dev/null +++ b/webkit/glue/resources/row_resize.cur diff --git a/webkit/glue/resources/search_cancel.png b/webkit/glue/resources/search_cancel.png Binary files differnew file mode 100644 index 0000000..49f3f47 --- /dev/null +++ b/webkit/glue/resources/search_cancel.png diff --git a/webkit/glue/resources/search_cancel_pressed.png b/webkit/glue/resources/search_cancel_pressed.png Binary files differnew file mode 100644 index 0000000..b699d81 --- /dev/null +++ b/webkit/glue/resources/search_cancel_pressed.png diff --git a/webkit/glue/resources/search_magnifier.png b/webkit/glue/resources/search_magnifier.png Binary files differnew file mode 100644 index 0000000..f9b8cae --- /dev/null +++ b/webkit/glue/resources/search_magnifier.png diff --git a/webkit/glue/resources/search_magnifier_results.png b/webkit/glue/resources/search_magnifier_results.png Binary files differnew file mode 100644 index 0000000..9aa1b36 --- /dev/null +++ b/webkit/glue/resources/search_magnifier_results.png diff --git a/webkit/glue/resources/textarea_resize_corner.png b/webkit/glue/resources/textarea_resize_corner.png Binary files differnew file mode 100644 index 0000000..023615e --- /dev/null +++ b/webkit/glue/resources/textarea_resize_corner.png diff --git a/webkit/glue/resources/vertical_text.cur b/webkit/glue/resources/vertical_text.cur Binary files differnew file mode 100644 index 0000000..3de04eb --- /dev/null +++ b/webkit/glue/resources/vertical_text.cur diff --git a/webkit/glue/resources/webkit_strings_am.xtb b/webkit/glue/resources/webkit_strings_am.xtb new file mode 100644 index 0000000..acba326 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_am.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="am"> +<translation id="4420062214988137980">የPlugin ጭነት ተሰናክሏል</translation> +<translation id="1235745349614807883">የቅርብ ጊዜ ፍለጋዎችን አስወግድ</translation> +<translation id="3825324228893189080">ተጨማሪ plugin ያስፈልጋል</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> plugin አልተጫነም</translation> +<translation id="5048533449481078685">ዝርዝር አመልካች</translation> +<translation id="4202807286478387388">ዝለል</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">እባክዎ <ph name="PLUGIN"/> plugin ለመጫን መፈለግዎን ያረጋግጡ። የሚያምኗቸውን plugins ብቻ መጫን ይኖርብዎታል።</translation> +<translation id="7658239707568436148">ሰርዝ</translation> +<translation id="795667975304826397">ምንም ፋይል አልተመረጠም</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> plugin ያስፈልጋል</translation> +<translation id="8662565117025751661">የሚወርድ plugin…</translation> +<translation id="8141602879876242471">ይህ ሊፈለግ የሚችል መረጃ ጠቋሚ ነው። የፍለጋ ቁልፍ ቃላት አስገባ፦</translation> +<translation id="6845533974506654842">ተጫን</translation> +<translation id="8244226242650769279">የምስል ካርታ</translation> +<translation id="1383141426028388991">ከ<ph name="URL"/> plugin ለመጫን አልተቻለም</translation> +<translation id="2548326553472216322">የቅርብ ጊዜ ፍለጋዎች የሉም</translation> +<translation id="5944544982112848342">2048(ከፍተኛ ደረጃ)</translation> +<translation id="3040011195152428237">አገናኝ</translation> +<translation id="8281246460978372009">plugin ከተጫነ በኋላ፣ ለማደስ እዚህ ይጫኑ</translation> +<translation id="7364796246159120393">ፋይል ምረጥ</translation> +<translation id="8964020114565522021">ፋይል ወደዚህ ጎትት</translation> +<translation id="838869780401515933">አመልክት</translation> +<translation id="2846343701378493991">1024(መካከለኛ ደረጃ)</translation> +<translation id="5476505524087279545">አታመልክት</translation> +<translation id="679352192834563463">ይህን ይዘት ለማሳየት ምንም plugin የለም</translation> +<translation id="3789841737615482174">ጫን</translation> +<translation id="6663448176199120256">የቅርብ ጊዜ ፍለጋዎችን</translation> +<translation id="3600343118165084788">plugin ለማውረድ እዚህ ይጫኑ</translation> +<translation id="6807599807928161586">የድር ክልል</translation> +<translation id="5939518447894949180">ዳግም አስጀምር</translation> +<translation id="3771786644471114952">Plugin አግኝ</translation> +<translation id="1842960171412779397">ምረጥ</translation> +<translation id="6119846243427417423">አንቃ</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ፋይሎች</translation> +<translation id="3926627843712816530">እባክዎ ይህን plugin ለመጫን መፈለግዎን ያረጋግጡ። የሚያምኗቸውን plugins ብቻ መጫን ይኖርብዎታል።</translation> +<translation id="4838490908464673667">የሚፈለገው plugin አልተጫነም</translation> +<translation id="8597182159515967513">ርእስ</translation> +<translation id="2653659639078652383">አስገባ</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ar.xtb b/webkit/glue/resources/webkit_strings_ar.xtb new file mode 100644 index 0000000..a558190 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ar.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ar"> +<translation id="4420062214988137980">إخفاق محاولة تثبيت المكوّن الإضافي</translation> +<translation id="1235745349614807883">محو آخر عمليات البحث</translation> +<translation id="3825324228893189080">مطلوب مكوّن إضافيّ آخر.</translation> +<translation id="2965480764085142436">المكوّن الإضافي <ph name="PLUGIN"/> غير متوفّر</translation> +<translation id="5048533449481078685">محدّد القائمة</translation> +<translation id="4202807286478387388">الدخول</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">الرّجاء تأكيد طلب تثبيت المكوّن الإضافي <ph name="PLUGIN"/>. يجب تثبيت المكوّنات الإضافية الموثوق بها فقط.</translation> +<translation id="7658239707568436148">إلغاء</translation> +<translation id="795667975304826397">ّلم يتمّ اختيار أيّ ملفّ</translation> +<translation id="1275511093094545429">يلزم المكوّن الإضافيّ <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">تحميل المكوّن الإضافي...</translation> +<translation id="8141602879876242471">يمكن البحث في هذا الفهرس بإدخال كلمات مفتاحية:</translation> +<translation id="6845533974506654842">اضغط</translation> +<translation id="8244226242650769279">مخطّط صورة</translation> +<translation id="1383141426028388991">فشل تثبيت المكون الإضافي من <ph name="URL"/></translation> +<translation id="2548326553472216322">لا عمليات بحث حديثة</translation> +<translation id="5944544982112848342">2048 (درجة عالية)</translation> +<translation id="3040011195152428237">رابط</translation> +<translation id="8281246460978372009">بعد تثبيت المكوّن الإضافي، انقر هنا لتحديث النافذة</translation> +<translation id="7364796246159120393">اختيار ملفّ</translation> +<translation id="8964020114565522021">سحب الملفّ إلى هنا</translation> +<translation id="838869780401515933">الاختيار</translation> +<translation id="2846343701378493991">1024 (درجة متوسطة)</translation> +<translation id="5476505524087279545">إزالة علامة الاختيار</translation> +<translation id="679352192834563463">لا يتوفر أي مكوّن إضافي لعرض هذا المحتوى</translation> +<translation id="3789841737615482174">تثبيت</translation> +<translation id="6663448176199120256">آخر عمليات البحث</translation> +<translation id="3600343118165084788">انقر هنا لتنزيل المكوّن الإضافي</translation> +<translation id="6807599807928161586">منطقة الويب</translation> +<translation id="5939518447894949180">إعادة</translation> +<translation id="3771786644471114952">جلب المكوّن الإضافي</translation> +<translation id="1842960171412779397">الاختيار</translation> +<translation id="6119846243427417423">تنشيط</translation> +<translation id="8444882422881193423">عدد الملفات: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">يرجى تأكيد طلب تثبيت المكوّن الإضافي. يجب تثبيت المكوّنات الإضافية الموثوق بها فقط.</translation> +<translation id="4838490908464673667">المكوّن الإضافيّ المطلوب غير متوفّر</translation> +<translation id="8597182159515967513">العنوان</translation> +<translation id="2653659639078652383">إرسال</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_bg.xtb b/webkit/glue/resources/webkit_strings_bg.xtb new file mode 100644 index 0000000..91d0b4b --- /dev/null +++ b/webkit/glue/resources/webkit_strings_bg.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bg"> +<translation id="4420062214988137980">Инсталирането на приставката не бе успешно</translation> +<translation id="1235745349614807883">Изчистване на скорошните търсения</translation> +<translation id="3825324228893189080">Изисква се допълнителна приставка</translation> +<translation id="2965480764085142436">Приставката за <ph name="PLUGIN"/> не е инсталирана</translation> +<translation id="5048533449481078685">списъчен показалец</translation> +<translation id="4202807286478387388">преминаване</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Моля, потвърдете, че искате да инсталирате приставката за <ph name="PLUGIN"/>. Инсталирайте само тези приставки, които считате за надеждни.</translation> +<translation id="7658239707568436148">Отказ</translation> +<translation id="795667975304826397">Няма избран файл</translation> +<translation id="1275511093094545429">Необходима е приставка за <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Приставката се изтегля...</translation> +<translation id="8141602879876242471">В този индекс може да се търси. Въведете ключови думи за търсене:</translation> +<translation id="6845533974506654842">натискане</translation> +<translation id="8244226242650769279">карта с изображения</translation> +<translation id="1383141426028388991">Неуспешно инсталиране на приставката от <ph name="URL"/></translation> +<translation id="2548326553472216322">Няма скорошни търсения</translation> +<translation id="5944544982112848342">2048 (висока степен на сложност)</translation> +<translation id="3040011195152428237">връзка</translation> +<translation id="8281246460978372009">След като инсталирате приставката, кликнете тук, за да опресните прозореца</translation> +<translation id="7364796246159120393">Избор на файл</translation> +<translation id="8964020114565522021">Плъзнете файла тук</translation> +<translation id="838869780401515933">отмятане</translation> +<translation id="2846343701378493991">1024 (средна степен на сложност)</translation> +<translation id="5476505524087279545">премахване на отметката</translation> +<translation id="679352192834563463">Няма приставка, с която може да се покаже това съдържание</translation> +<translation id="3789841737615482174">Инсталиране</translation> +<translation id="6663448176199120256">Скорошни търсения</translation> +<translation id="3600343118165084788">Кликнете тук, за да изтеглите приставката</translation> +<translation id="6807599807928161586">уеб зона</translation> +<translation id="5939518447894949180">Повторно задаване</translation> +<translation id="3771786644471114952">Изтегляне на приставката</translation> +<translation id="1842960171412779397">Избиране</translation> +<translation id="6119846243427417423">активиране</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> файла</translation> +<translation id="3926627843712816530">Моля, потвърдете, че искате да инсталирате тази приставка. Инсталирайте само тези приставки, които считате за надеждни.</translation> +<translation id="4838490908464673667">Необходимата приставка не е инсталирана</translation> +<translation id="8597182159515967513">заглавие</translation> +<translation id="2653659639078652383">Изпращане</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_bn.xtb b/webkit/glue/resources/webkit_strings_bn.xtb new file mode 100644 index 0000000..098985f --- /dev/null +++ b/webkit/glue/resources/webkit_strings_bn.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bn"> +<translation id="4420062214988137980">প্ল্যাগইন ইনস্টলেশন ব্যর্থ</translation> +<translation id="1235745349614807883">সাম্প্রতিক অনুসন্ধানগুলি সাফ করুন</translation> +<translation id="3825324228893189080">অতিরিক্ত প্ল্যাগইন প্রয়োজনীয়</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> প্ল্যাগইন ইনস্টল হয় নি</translation> +<translation id="5048533449481078685">তালিকা নির্দেশক</translation> +<translation id="4202807286478387388">লাফ দিন</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">দয়া করে নিশ্চিত করুন, আপনি <ph name="PLUGIN"/> প্ল্যাগইন ইনস্টল করতে চান৷ আপনি বিশ্বাস করেন কেবল এমন প্ল্যাগইনই ইনস্টল করা উচিত৷</translation> +<translation id="7658239707568436148">বাতিল</translation> +<translation id="795667975304826397">কোনও ফাইল চয়ন করা হয় নি</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> প্ল্যাগইন প্রয়োজনীয়</translation> +<translation id="8662565117025751661">প্ল্যাগইন ডাউনলোড হচ্ছে...</translation> +<translation id="8141602879876242471">এটি অনুসন্ধানযোগ্য সূচি৷ অনুসন্ধানের মূলশব্দ প্রবেশ করান:</translation> +<translation id="6845533974506654842">টিপুন</translation> +<translation id="8244226242650769279">ছবি মানচিত্র</translation> +<translation id="1383141426028388991"><ph name="URL"/> থেকে প্ল্যাগইন ইনস্টল করতে ব্যর্থ হয়েছে</translation> +<translation id="2548326553472216322">কোন সাম্প্রতিক অনুসন্ধান নেই</translation> +<translation id="5944544982112848342">2048 (উচ্চ গ্রেড)</translation> +<translation id="3040011195152428237">লিঙ্ক</translation> +<translation id="8281246460978372009">প্ল্যাগইন ইনস্টল করার পরে, রিফ্রেশ করার জন্য এখানে ক্লিক করুন</translation> +<translation id="7364796246159120393">ফাইল চয়ন করুন</translation> +<translation id="8964020114565522021">ফাইল এখানে টেনে আনুন</translation> +<translation id="838869780401515933">চেক করুন</translation> +<translation id="2846343701378493991">1024 (মধ্যম গ্রেড)</translation> +<translation id="5476505524087279545">আনচেক</translation> +<translation id="679352192834563463">এই সামগ্রী প্রদর্শন করার জন্য কোনও প্ল্যাগইন উপলব্ধ নেই</translation> +<translation id="3789841737615482174">ইনস্টল করুন</translation> +<translation id="6663448176199120256">সাম্প্রতিক অনুসন্ধানগুলি</translation> +<translation id="3600343118165084788">প্ল্যাগ ডাউনলোড করার জন্য এখানে ডাউনলোড করুন</translation> +<translation id="6807599807928161586">ওয়েব এলাকা</translation> +<translation id="5939518447894949180">রিসেট করুন</translation> +<translation id="3771786644471114952">প্ল্যাগইন প্রাপ্ত করুন</translation> +<translation id="1842960171412779397">নির্বাচন করুন</translation> +<translation id="6119846243427417423">সক্রিয় করুন</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> টি ফাইল</translation> +<translation id="3926627843712816530">আপনি প্ল্যাগইন ইন্স্টল করতে চান তা দয়া করে নিশ্চিত করুন৷ আপনি বিশ্বাস করেন কেবল এমন প্ল্যাগইনই ইনস্টল করা উচিত৷</translation> +<translation id="4838490908464673667">প্রয়োজনীয় প্ল্যাগইন ইনস্টল করা নেই</translation> +<translation id="8597182159515967513">শিরোনামা</translation> +<translation id="2653659639078652383">জমা</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ca.xtb b/webkit/glue/resources/webkit_strings_ca.xtb new file mode 100644 index 0000000..b5b1aa7 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ca.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ca"> +<translation id="4420062214988137980">Error en la instal·lació del complement</translation> +<translation id="1235745349614807883">Esborra les cerques recents</translation> +<translation id="3825324228893189080">Es necessita un complement addicional</translation> +<translation id="2965480764085142436">El complement <ph name="PLUGIN"/> no està instal·lat</translation> +<translation id="5048533449481078685">marcador de llistes</translation> +<translation id="4202807286478387388">salta</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirma que vols instal·lar el complement <ph name="PLUGIN"/>. Només hauries d'instal·lar complements fiables.</translation> +<translation id="7658239707568436148">Cancel·la</translation> +<translation id="795667975304826397">No heu seleccionat cap fitxer.</translation> +<translation id="1275511093094545429">Es necessita el complement <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">S'està baixant el complement...</translation> +<translation id="8141602879876242471">És un índex on es poden realitzar cerques. Introdueix els termes de cerca:</translation> +<translation id="6845533974506654842">prem</translation> +<translation id="8244226242650769279">mapa d'imatges</translation> +<translation id="1383141426028388991">S'ha produït un error en instal·lar el connector des de <ph name="URL"/></translation> +<translation id="2548326553472216322">No hi ha cerques recents</translation> +<translation id="5944544982112848342">2048 (Gran)</translation> +<translation id="3040011195152428237">enllaç</translation> +<translation id="8281246460978372009">Després d'instal·lar el complement, feu clic aquí per actualitzar.</translation> +<translation id="7364796246159120393">Selecciona el fitxer</translation> +<translation id="8964020114565522021">Arrossegueu el fitxer aquí.</translation> +<translation id="838869780401515933">marca</translation> +<translation id="2846343701378493991">1024 (Mitjà)</translation> +<translation id="5476505524087279545">desmarca</translation> +<translation id="679352192834563463">No hi ha cap complement disponible per mostrar aquest contingut</translation> +<translation id="3789841737615482174">Instal·la</translation> +<translation id="6663448176199120256">Cerques recents</translation> +<translation id="3600343118165084788">Feu clic aquí per baixar el complement</translation> +<translation id="6807599807928161586">àrea web</translation> +<translation id="5939518447894949180">Restablir</translation> +<translation id="3771786644471114952">Obtén el complement</translation> +<translation id="1842960171412779397">selecciona</translation> +<translation id="6119846243427417423">activa</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> fitxers</translation> +<translation id="3926627843712816530">Confirma que vols instal·lar aquest complement. Només hauries d'instal·lar complements fiables.</translation> +<translation id="4838490908464673667">El complement necessari no està instal·lat</translation> +<translation id="8597182159515967513">Capçalera</translation> +<translation id="2653659639078652383">Envia</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_cs.xtb b/webkit/glue/resources/webkit_strings_cs.xtb new file mode 100644 index 0000000..33026e2 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_cs.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="cs"> +<translation id="4420062214988137980">Nepodařilo se nainstalovat plugin</translation> +<translation id="1235745349614807883">Smazat nedávná vyhledávání</translation> +<translation id="3825324228893189080">Je potřeba další plugin</translation> +<translation id="2965480764085142436">Není nainstalován plugin <ph name="PLUGIN"/></translation> +<translation id="5048533449481078685">značka seznamu</translation> +<translation id="4202807286478387388">přejít</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Potvrďte prosím, že si přejete instalovat plugin <ph name="PLUGIN"/>. Měli byste instalovat pouze takové pluginy, kterým důvěřujete.</translation> +<translation id="7658239707568436148">Zrušit</translation> +<translation id="795667975304826397">Soubor nevybrán</translation> +<translation id="1275511093094545429">Je potřeba plugin <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Stahování pluginu...</translation> +<translation id="8141602879876242471">Toto je prohledávatelný index. Zadejte hledaná klíčová slova:</translation> +<translation id="6845533974506654842">zmáčknout</translation> +<translation id="8244226242650769279">obrázková mapa</translation> +<translation id="1383141426028388991">Nepodařilo se nainstalovat plugin z adresy <ph name="URL"/></translation> +<translation id="2548326553472216322">Žádná nedávná vyhledávání</translation> +<translation id="5944544982112848342">2048 (vysoká kvalita)</translation> +<translation id="3040011195152428237">odkaz</translation> +<translation id="8281246460978372009">Po instalaci pluginu klikněte sem pro obnovení</translation> +<translation id="7364796246159120393">Vybrat soubor</translation> +<translation id="8964020114565522021">Přetáhnout soubor sem</translation> +<translation id="838869780401515933">zaškrtnout</translation> +<translation id="2846343701378493991">1024 (Střední kvalita)</translation> +<translation id="5476505524087279545">odstranit zaškrtnutí</translation> +<translation id="679352192834563463">Není k dispozici žádný plugin pro zobrazení tohoto obsahu</translation> +<translation id="3789841737615482174">Instalovat</translation> +<translation id="6663448176199120256">Nedávná vyhledávání</translation> +<translation id="3600343118165084788">Pro stažení pluginu klikněte sem</translation> +<translation id="6807599807928161586">oblast webu</translation> +<translation id="5939518447894949180">Resetovat</translation> +<translation id="3771786644471114952">Získat plugin</translation> +<translation id="1842960171412779397">zvolit</translation> +<translation id="6119846243427417423">aktivovat</translation> +<translation id="8444882422881193423">Počet souborů: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Potvrďte, prosím, že si přejete instalovat tento plugin. Měli byste instalovat pouze takové pluginy, kterým důvěřujete.</translation> +<translation id="4838490908464673667">Požadovaný plugin není nainstalován</translation> +<translation id="8597182159515967513">záhlaví</translation> +<translation id="2653659639078652383">Odeslat</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_da.xtb b/webkit/glue/resources/webkit_strings_da.xtb new file mode 100644 index 0000000..a923897 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_da.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="da"> +<translation id="4420062214988137980">Installation af plugin mislykkedes</translation> +<translation id="1235745349614807883">Slet nylige søgninger</translation> +<translation id="3825324228893189080">Ekstra plugin påkrævet</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> plugin er ikke installeret</translation> +<translation id="5048533449481078685">listemarkering</translation> +<translation id="4202807286478387388">hop</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Bekræft venligst, at du ønsker at installere plugin'et <ph name="PLUGIN"/>. Du bør udelukkende installere plugins, som du stoler på.</translation> +<translation id="7658239707568436148">Annuller</translation> +<translation id="795667975304826397">Der er ikke valgt nogen fil</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> Plugin påkrævet</translation> +<translation id="8662565117025751661">Downloader plugin ...</translation> +<translation id="8141602879876242471">Der kan søges i dette indeks. Indtast søge-nøgleord:</translation> +<translation id="6845533974506654842">tryk</translation> +<translation id="8244226242650769279">billedekort</translation> +<translation id="1383141426028388991">Mislykket installation af plugin fra <ph name="URL"/></translation> +<translation id="2548326553472216322">Ingen nylige søgninger</translation> +<translation id="5944544982112848342">2048 (Høj klasse)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Når du har installeret plugin'et, skal du klikke her for at opdatere</translation> +<translation id="7364796246159120393">Vælg fil</translation> +<translation id="8964020114565522021">Træk filer hertil</translation> +<translation id="838869780401515933">marker</translation> +<translation id="2846343701378493991">1024 (Mellemklasse)</translation> +<translation id="5476505524087279545">fjern markering</translation> +<translation id="679352192834563463">Intet tilgængeligt plugin kan vise dette indhold</translation> +<translation id="3789841737615482174">Installer</translation> +<translation id="6663448176199120256">Nylige søgninger</translation> +<translation id="3600343118165084788">Klik her for at downloade plugin</translation> +<translation id="6807599807928161586">webområde</translation> +<translation id="5939518447894949180">Nulstil</translation> +<translation id="3771786644471114952">Hent plugin</translation> +<translation id="1842960171412779397">vælg</translation> +<translation id="6119846243427417423">aktiver</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> filer</translation> +<translation id="3926627843712816530">Bekræft venligst, at du gerne vil installere dette plugin. Du bør udelukkende installere plugins, som du stoler på.</translation> +<translation id="4838490908464673667">Det krævede plugin er ikke installeret</translation> +<translation id="8597182159515967513">overskrift</translation> +<translation id="2653659639078652383">Indsend</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_de.xtb b/webkit/glue/resources/webkit_strings_de.xtb new file mode 100644 index 0000000..76411ed --- /dev/null +++ b/webkit/glue/resources/webkit_strings_de.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="de"> +<translation id="4420062214988137980">Plug-in-Installation fehlgeschlagen</translation> +<translation id="1235745349614807883">Vor kurzem durchgeführte Suchanfragen löschen</translation> +<translation id="3825324228893189080">Zusätzliches Plug-in erforderlich</translation> +<translation id="2965480764085142436">Plug-in <ph name="PLUGIN"/> nicht installiert</translation> +<translation id="5048533449481078685">Listenmarkierung</translation> +<translation id="4202807286478387388">springen</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Bestätigen Sie, dass Sie das Plug-in <ph name="PLUGIN"/> installieren möchten. Sie sollten nur vertrauenswürdige Plug-ins installieren.</translation> +<translation id="7658239707568436148">Abbrechen</translation> +<translation id="795667975304826397">Keine Datei ausgewählt</translation> +<translation id="1275511093094545429">Plug-in <ph name="PLUGIN"/> erforderlich</translation> +<translation id="8662565117025751661">Plug-in wird heruntergeladen...</translation> +<translation id="8141602879876242471">Dieser Index kann durchsucht werden. Geben Sie Suchbegriffe ein:</translation> +<translation id="6845533974506654842">klicken</translation> +<translation id="8244226242650769279">Imagemap</translation> +<translation id="1383141426028388991">Installation des Plug-ins von <ph name="URL"/> fehlgeschlagen</translation> +<translation id="2548326553472216322">Keine vor kurzem durchgeführte Suchanfragen</translation> +<translation id="5944544982112848342">2048 (High Grade)</translation> +<translation id="3040011195152428237">Link</translation> +<translation id="8281246460978372009">Klicken Sie nach der Installation des Plug-ins hier, um eine Aktualisierung durchzuführen.</translation> +<translation id="7364796246159120393">Datei auswählen</translation> +<translation id="8964020114565522021">Datei hier ablegen</translation> +<translation id="838869780401515933">auswählen</translation> +<translation id="2846343701378493991">1024 (mittlere Stufe)</translation> +<translation id="5476505524087279545">Auswahl aufheben</translation> +<translation id="679352192834563463">Kein Plug-in zum Anzeigen dieses Contents verfügbar</translation> +<translation id="3789841737615482174">Installieren</translation> +<translation id="6663448176199120256">Vor kurzem durchgeführte Suchanfragen</translation> +<translation id="3600343118165084788">Klicken Sie hier, um das Plug-in herunterzuladen.</translation> +<translation id="6807599807928161586">Webbereich</translation> +<translation id="5939518447894949180">Zurücksetzen</translation> +<translation id="3771786644471114952">Plug-in abrufen</translation> +<translation id="1842960171412779397">auswählen</translation> +<translation id="6119846243427417423">aktivieren</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> Dateien</translation> +<translation id="3926627843712816530">Bestätigen Sie, dass Sie dieses Plug-in installieren möchten. Sie sollten nur vertrauenswürdige Plug-ins installieren.</translation> +<translation id="4838490908464673667">Das erforderliche Plug-in ist nicht installiert</translation> +<translation id="8597182159515967513">Kopfzeile</translation> +<translation id="2653659639078652383">Senden</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_el.xtb b/webkit/glue/resources/webkit_strings_el.xtb new file mode 100644 index 0000000..dd75011 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_el.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="el"> +<translation id="4420062214988137980">Η εγκατάσταση της προσθήκης απέτυχε</translation> +<translation id="1235745349614807883">Εκκαθάριση πρόσφατων αναζητήσεων</translation> +<translation id="3825324228893189080">Απαιτείται επιπλέον προσθήκη</translation> +<translation id="2965480764085142436">Η προσθήκη <ph name="PLUGIN"/> δεν έχει εγκατασταθεί</translation> +<translation id="5048533449481078685">δείκτης λίστας</translation> +<translation id="4202807286478387388">μεταπήδηση</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Επιβεβαιώστε ότι θέλετε να εγκαταστήσετε την προσθήκη <ph name="PLUGIN"/>. Πρέπει να εγκαθιστάτε μόνο προσθήκες που θεωρείτε αξιόπιστες.</translation> +<translation id="7658239707568436148">Ακύρωση</translation> +<translation id="795667975304826397">Δεν έχει επιλεγεί κανένα αρχείο</translation> +<translation id="1275511093094545429">Χρειάζεται η προσθήκη <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Λήψη προσθήκης...</translation> +<translation id="8141602879876242471">Πρόκειται για ευρετήριο με δυνατότητα αναζήτησης. Πληκτρολογήστε λέξεις-κλειδιά αναζήτησης:</translation> +<translation id="6845533974506654842">πατήστε</translation> +<translation id="8244226242650769279">χάρτης εικόνας</translation> +<translation id="1383141426028388991">Η εγκατάσταση της προσθήκης από τη διεύθυνση <ph name="URL"/> απέτυχε</translation> +<translation id="2548326553472216322">Δεν υπάρχουν πρόσφατες αναζητήσεις</translation> +<translation id="5944544982112848342">2048 (Υψηλός βαθμός)</translation> +<translation id="3040011195152428237">σύνδεσμος</translation> +<translation id="8281246460978372009">Μετά την εγκατάσταση της προσθήκης, κάντε κλικ εδώ για ανανέωση</translation> +<translation id="7364796246159120393">Επιλογή αρχείου</translation> +<translation id="8964020114565522021">Σύρετε το αρχείο εδώ</translation> +<translation id="838869780401515933">ενεργοποίηση</translation> +<translation id="2846343701378493991">1024 (Μέτριος βαθμός)</translation> +<translation id="5476505524087279545">απενεργοποίηση</translation> +<translation id="679352192834563463">Δεν υπάρχει διαθέσιμη προσθήκη για την εμφάνιση του περιεχομένου</translation> +<translation id="3789841737615482174">Εγκατάσταση</translation> +<translation id="6663448176199120256">Πρόσφατες αναζητήσεις</translation> +<translation id="3600343118165084788">Κάντε κλικ εδώ για να κατεβάσετε την προσθήκη</translation> +<translation id="6807599807928161586">περιοχή ιστού</translation> +<translation id="5939518447894949180">Επαναφορά</translation> +<translation id="3771786644471114952">Λήψη προσθήκης</translation> +<translation id="1842960171412779397">επιλογή</translation> +<translation id="6119846243427417423">ενεργοποίηση</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> αρχεία</translation> +<translation id="3926627843712816530">Επιβεβαιώστε ότι θέλετε να εγκαταστήσετε αυτή την προσθήκη. Πρέπει να εγκαθιστάτε μόνο προσθήκες που θεωρείτε αξιόπιστες.</translation> +<translation id="4838490908464673667">Η απαιτούμενη προσθήκη δεν έχει εγκατασταθεί</translation> +<translation id="8597182159515967513">επικεφαλίδα</translation> +<translation id="2653659639078652383">Υποβολή</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_en-GB.xtb b/webkit/glue/resources/webkit_strings_en-GB.xtb new file mode 100644 index 0000000..ed7a1b6 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_en-GB.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="en-GB"> +<translation id="4420062214988137980">Plug-in installation failed</translation> +<translation id="1235745349614807883">Clear Recent Searches</translation> +<translation id="3825324228893189080">Additional plug-in needed</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> plug-in is not installed</translation> +<translation id="5048533449481078685">list marker</translation> +<translation id="4202807286478387388">jump</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Please confirm that you would like to install the <ph name="PLUGIN"/> plug-in. You should only install plug-ins that you trust.</translation> +<translation id="7658239707568436148">Cancel</translation> +<translation id="795667975304826397">No file chosen</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> plug-in needed</translation> +<translation id="8662565117025751661">Downloading plug-in...</translation> +<translation id="8141602879876242471">This is a searchable index. Enter search keywords:</translation> +<translation id="6845533974506654842">press</translation> +<translation id="8244226242650769279">image map</translation> +<translation id="1383141426028388991">Failed to install plug-in from <ph name="URL"/></translation> +<translation id="2548326553472216322">No recent searches</translation> +<translation id="5944544982112848342">2048 (High Grade)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">After installing the plug-in, click here to refresh</translation> +<translation id="7364796246159120393">Choose File</translation> +<translation id="8964020114565522021">Drag file here</translation> +<translation id="838869780401515933">tick</translation> +<translation id="2846343701378493991">1024 (Medium Grade)</translation> +<translation id="5476505524087279545">untick</translation> +<translation id="679352192834563463">No plug-in available to display this content</translation> +<translation id="3789841737615482174">Install</translation> +<translation id="6663448176199120256">Recent Searches</translation> +<translation id="3600343118165084788">Click here to download plug-in</translation> +<translation id="6807599807928161586">web area</translation> +<translation id="5939518447894949180">Reset</translation> +<translation id="3771786644471114952">Get Plug-in</translation> +<translation id="1842960171412779397">select</translation> +<translation id="6119846243427417423">activate</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> files</translation> +<translation id="3926627843712816530">Please confirm that you would like to install this plug-in. You should only install plug-ins that you trust.</translation> +<translation id="4838490908464673667">The required plug-in is not installed</translation> +<translation id="8597182159515967513">heading</translation> +<translation id="2653659639078652383">Submit</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_es-419.xtb b/webkit/glue/resources/webkit_strings_es-419.xtb new file mode 100644 index 0000000..d7e05f4 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_es-419.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es-419"> +<translation id="4420062214988137980">Error en la instalación del plug-in</translation> +<translation id="1235745349614807883">Eliminar búsquedas recientes</translation> +<translation id="3825324228893189080">Se necesita un plug-in adicional</translation> +<translation id="2965480764085142436">El plug-in <ph name="PLUGIN"/> no está instalado</translation> +<translation id="5048533449481078685">marcador de listas</translation> +<translation id="4202807286478387388">saltar</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirma que deseas instalar el plug-in <ph name="PLUGIN"/>. Solo debes instalar plug-in fiables.</translation> +<translation id="7658239707568436148">Cancelar</translation> +<translation id="795667975304826397">No se eligió ningún archivo</translation> +<translation id="1275511093094545429">Se requiere el plug-in <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Descargando plug-in...</translation> +<translation id="8141602879876242471">Se trata de un índice que admite búsquedas. Escribe las palabras clave de búsqueda:</translation> +<translation id="6845533974506654842">hacer clic</translation> +<translation id="8244226242650769279">mapa de imágenes</translation> +<translation id="1383141426028388991">Error al instalar el complemento desde <ph name="URL"/></translation> +<translation id="2548326553472216322">No hay búsquedas recientes</translation> +<translation id="5944544982112848342">2048 (Grado elevado)</translation> +<translation id="3040011195152428237">enlace</translation> +<translation id="8281246460978372009">Tras instalar el complemento, haz clic aquí para actualizar.</translation> +<translation id="7364796246159120393">Seleccionar archivo</translation> +<translation id="8964020114565522021">Arrastre el archivo hasta aquí</translation> +<translation id="838869780401515933">marcar</translation> +<translation id="2846343701378493991">1024 (Mediano)</translation> +<translation id="5476505524087279545">desmarcar</translation> +<translation id="679352192834563463">No hay ningún plug-in disponible para mostrar este contenido</translation> +<translation id="3789841737615482174">Instalar</translation> +<translation id="6663448176199120256">Búsquedas recientes</translation> +<translation id="3600343118165084788">Haz clic aquí para descargar el plug-in.</translation> +<translation id="6807599807928161586">área web</translation> +<translation id="5939518447894949180">Restablecer</translation> +<translation id="3771786644471114952">Obtener plug-in</translation> +<translation id="1842960171412779397">seleccionar</translation> +<translation id="6119846243427417423">activar</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> archivos</translation> +<translation id="3926627843712816530">Confirma que deseas instalar este plug-in. Solo debes instalar plug-in fiables.</translation> +<translation id="4838490908464673667">El plug-in necesario no está instalado.</translation> +<translation id="8597182159515967513">cabecera</translation> +<translation id="2653659639078652383">Enviar</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_es.xtb b/webkit/glue/resources/webkit_strings_es.xtb new file mode 100644 index 0000000..c95525d --- /dev/null +++ b/webkit/glue/resources/webkit_strings_es.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es"> +<translation id="4420062214988137980">Error en la instalación del plug-in</translation> +<translation id="1235745349614807883">Eliminar búsquedas recientes</translation> +<translation id="3825324228893189080">Se necesita un plug-in adicional</translation> +<translation id="2965480764085142436">El plug-in <ph name="PLUGIN"/> no está instalado</translation> +<translation id="5048533449481078685">marcador de listas</translation> +<translation id="4202807286478387388">saltar</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirma que deseas instalar el plug-in <ph name="PLUGIN"/>. Solo debes instalar plug-in fiables.</translation> +<translation id="7658239707568436148">Cancelar</translation> +<translation id="795667975304826397">No se ha seleccionado ningun archivo</translation> +<translation id="1275511093094545429">Se requiere el plug-in <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Descargando plug-in...</translation> +<translation id="8141602879876242471">Se trata de un índice que admite búsquedas. Introduce las palabras clave de búsqueda:</translation> +<translation id="6845533974506654842">pulsar</translation> +<translation id="8244226242650769279">mapa de imágenes</translation> +<translation id="1383141426028388991">Error al instalar el plugin desde <ph name="URL"/></translation> +<translation id="2548326553472216322">No hay búsquedas recientes</translation> +<translation id="5944544982112848342">2048 (Grado elevado)</translation> +<translation id="3040011195152428237">enlace</translation> +<translation id="8281246460978372009">Tras instalar el complemento, haz clic aquí para actualizar.</translation> +<translation id="7364796246159120393">Seleccionar archivo</translation> +<translation id="8964020114565522021">Arrastrar archivo hasta aquí</translation> +<translation id="838869780401515933">marcar</translation> +<translation id="2846343701378493991">1024 (Mediano)</translation> +<translation id="5476505524087279545">desmarcar</translation> +<translation id="679352192834563463">No hay ningún plug-in disponible para mostrar este contenido</translation> +<translation id="3789841737615482174">Instalar</translation> +<translation id="6663448176199120256">Búsquedas recientes</translation> +<translation id="3600343118165084788">Haz clic aquí para descargar el plug-in.</translation> +<translation id="6807599807928161586">área web</translation> +<translation id="5939518447894949180">Restablecer</translation> +<translation id="3771786644471114952">Obtener plug-in</translation> +<translation id="1842960171412779397">seleccionar</translation> +<translation id="6119846243427417423">activar</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> archivos</translation> +<translation id="3926627843712816530">Confirma que deseas instalar este plug-in. Solo debes instalar plug-in fiables.</translation> +<translation id="4838490908464673667">El plug-in necesario no está instalado.</translation> +<translation id="8597182159515967513">cabecera</translation> +<translation id="2653659639078652383">Enviar</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_et.xtb b/webkit/glue/resources/webkit_strings_et.xtb new file mode 100644 index 0000000..c449c06 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_et.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="et"> +<translation id="4420062214988137980">Lisandmooduli install ebaõnnestus</translation> +<translation id="1235745349614807883">Kustuta viimased otsingud</translation> +<translation id="3825324228893189080">Vajalik täiendav lisandmoodul</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> lisandmoodul ei ole installitud</translation> +<translation id="5048533449481078685">loendilooja</translation> +<translation id="4202807286478387388">liigu</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Palun kinnitage, et soovite installida selle lisandmooduli <ph name="PLUGIN"/>. Te peaksite installima lisandmooduleid, mida usaldate.</translation> +<translation id="7658239707568436148">Loobu</translation> +<translation id="795667975304826397">Ühtegi faili pole valitud</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> vajalik lisandmoodul</translation> +<translation id="8662565117025751661">Lisandmooduli allalaadimine...</translation> +<translation id="8141602879876242471">See on otsitav indeks. Sisestage otsingu jaoks märksõnad:</translation> +<translation id="6845533974506654842">vajuta</translation> +<translation id="8244226242650769279">hüperpilt</translation> +<translation id="1383141426028388991">Lisandmooduli installimine aadressilt <ph name="URL"/> ebaõnnestus.</translation> +<translation id="2548326553472216322">Pole viimaseid otsingud</translation> +<translation id="5944544982112848342">2048 (kõrge)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Pärast lisandmooduli installimist klõpsake värskendamiseks siia</translation> +<translation id="7364796246159120393">Vali fail</translation> +<translation id="8964020114565522021">Lohistage fail siia</translation> +<translation id="838869780401515933">mrgista</translation> +<translation id="2846343701378493991">1024 (keskmine)</translation> +<translation id="5476505524087279545">eemalda mrgistus</translation> +<translation id="679352192834563463">Selle sisu kuvamiseks pole saadaval lisandmoodulit</translation> +<translation id="3789841737615482174">Installi</translation> +<translation id="6663448176199120256">Viimased otsingud</translation> +<translation id="3600343118165084788">Lisandmooduli allalaadimiseks klõpsake siia.</translation> +<translation id="6807599807928161586">veebiala</translation> +<translation id="5939518447894949180">Lähtesta</translation> +<translation id="3771786644471114952">Hangi lisandmoodul</translation> +<translation id="1842960171412779397">vali</translation> +<translation id="6119846243427417423">aktiveeri</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> faili</translation> +<translation id="3926627843712816530">Palun kinnitage, et soovite installida selle lisandmooduli. Te peaksite installima lisandmooduleid, mida usaldate.</translation> +<translation id="4838490908464673667">Nõutav lisandmoodul pole installitud</translation> +<translation id="8597182159515967513">pealkiri</translation> +<translation id="2653659639078652383">Esita</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_fi.xtb b/webkit/glue/resources/webkit_strings_fi.xtb new file mode 100644 index 0000000..50f3a69 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_fi.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fi"> +<translation id="4420062214988137980">Laajennuksen asennus epäonnistui</translation> +<translation id="1235745349614807883">Poista viimeisimmät haut</translation> +<translation id="3825324228893189080">Lisälaajennus tarvitaan</translation> +<translation id="2965480764085142436">Laajennusta <ph name="PLUGIN"/> ei asennettu</translation> +<translation id="5048533449481078685">luettelon merkitsijä</translation> +<translation id="4202807286478387388">siirry</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Vahvista, että haluat asentaa laajennuksen <ph name="PLUGIN"/>. Kannattaa asentaa vain luotettavia laajennuksia.</translation> +<translation id="7658239707568436148">Peruuta</translation> +<translation id="795667975304826397">Ei valittua tiedostoa</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> laajennus tarvitaan</translation> +<translation id="8662565117025751661">Ladataan laajennusta...</translation> +<translation id="8141602879876242471">Tämä on haettavissa oleva hakemisto. Anna hakusanat:</translation> +<translation id="6845533974506654842">paina</translation> +<translation id="8244226242650769279">kuvakartta</translation> +<translation id="1383141426028388991">Laajennuksen asennus osoitteesta <ph name="URL"/> epäonnistui</translation> +<translation id="2548326553472216322">Ei viimeisimpiä hakuja</translation> +<translation id="5944544982112848342">2048 (korkea taso)</translation> +<translation id="3040011195152428237">linkki</translation> +<translation id="8281246460978372009">Päivitä laajennuksen asennuksen jälkeen napsauttamalla tästä</translation> +<translation id="7364796246159120393">Valitse tiedosto</translation> +<translation id="8964020114565522021">Vedä tiedosto tähän</translation> +<translation id="838869780401515933">valitse</translation> +<translation id="2846343701378493991">1024 (keskitaso)</translation> +<translation id="5476505524087279545">poista valinta</translation> +<translation id="679352192834563463">Tämän sisällön näyttämiseen ei ole saatavissa laajennusta</translation> +<translation id="3789841737615482174">Asenna</translation> +<translation id="6663448176199120256">Viimeisimmät haut</translation> +<translation id="3600343118165084788">Lataa laajennus napsauttamalla tätä</translation> +<translation id="6807599807928161586">verkkoalue</translation> +<translation id="5939518447894949180">Tyhjennä</translation> +<translation id="3771786644471114952">Hae laajennus</translation> +<translation id="1842960171412779397">Valitse</translation> +<translation id="6119846243427417423">aktivoi</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> tiedostoa</translation> +<translation id="3926627843712816530">Vahvista, että haluat asentaa tämän laajennuksen. Kannattaa asentaa vain luotettavia laajennuksia.</translation> +<translation id="4838490908464673667">Vaadittua laajennusta ei ole asennettu</translation> +<translation id="8597182159515967513">otsikko</translation> +<translation id="2653659639078652383">Lähetä</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_fil.xtb b/webkit/glue/resources/webkit_strings_fil.xtb new file mode 100644 index 0000000..5ed7e04 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_fil.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fil"> +<translation id="4420062214988137980">Nabigong pag-install sa plugin</translation> +<translation id="1235745349614807883">Lisiman ang Kasalukuyang Mga Paghahanap</translation> +<translation id="3825324228893189080">Kailangan ng karagdagang plugin</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> hindi na-install ang plugin</translation> +<translation id="5048533449481078685">Ilista ang marker</translation> +<translation id="4202807286478387388">tumalon</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Mangyaring kumpirmahin na nais mong i-install ang <ph name="PLUGIN"/> plugin. Dapat mo lamang i-install ang mga plugin na pinagkakatiwalaan mo.</translation> +<translation id="7658239707568436148">Ikansela</translation> +<translation id="795667975304826397">Walang napiling file</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> kinakailangang plugin</translation> +<translation id="8662565117025751661">Nagda-download ng plugin...</translation> +<translation id="8141602879876242471">Isa itong paghahanap ng index. Ipasok ang paghahanap sa mga keyword:</translation> +<translation id="6845533974506654842">pindutin</translation> +<translation id="8244226242650769279">mapa ng imahe</translation> +<translation id="1383141426028388991">Nabigong i-install ang plugin mula sa <ph name="URL"/></translation> +<translation id="2548326553472216322">Walang kamakailang mga paghahanap</translation> +<translation id="5944544982112848342">2048 (Pinakamataas na Marka)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Matapos ang pag-install ng plugin, mag-click dito upang mag-refresh</translation> +<translation id="7364796246159120393">Pumili ng File</translation> +<translation id="8964020114565522021">Kaldkarin dito ang file</translation> +<translation id="838869780401515933">I-tsek</translation> +<translation id="2846343701378493991">1024 (Katamtamang Grado)</translation> +<translation id="5476505524087279545">i-uncheck</translation> +<translation id="679352192834563463">Walang magagamit na plugin na ipapakita sa nilalaman nito</translation> +<translation id="3789841737615482174">Install</translation> +<translation id="6663448176199120256">Kasalukuyang Mga Paghahanap</translation> +<translation id="3600343118165084788">Mag-click dito upagn mai-download ang plugin</translation> +<translation id="6807599807928161586">web area</translation> +<translation id="5939518447894949180">I-reset</translation> +<translation id="3771786644471114952">Kunin ang Plugin</translation> +<translation id="1842960171412779397">piliin</translation> +<translation id="6119846243427417423">isaaktibo</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> mga file</translation> +<translation id="3926627843712816530">Mangyaring kumpirmahin na dapat gusto mong i-install sa plugin na ito. Dapat mo lamang i-install ang mga plugin na pinagkakatiwalaan mo.</translation> +<translation id="4838490908464673667">Ang kinakailangan na plugin ay hindi na-install</translation> +<translation id="8597182159515967513">heading</translation> +<translation id="2653659639078652383">Isumite</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_fr.xtb b/webkit/glue/resources/webkit_strings_fr.xtb new file mode 100644 index 0000000..1f78547 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_fr.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fr"> +<translation id="4420062214988137980">Échec de l'installation du plug-in</translation> +<translation id="1235745349614807883">Effacer les recherches récentes</translation> +<translation id="3825324228893189080">Plug-in supplémentaire requis</translation> +<translation id="2965480764085142436">Le plug-in <ph name="PLUGIN"/> n'est pas installé.</translation> +<translation id="5048533449481078685">marqueur de liste</translation> +<translation id="4202807286478387388">accéder</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/> × <ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Veuillez confirmer que vous souhaitez installer le plug-in <ph name="PLUGIN"/>. Veillez à installer uniquement des plug-ins approuvés.</translation> +<translation id="7658239707568436148">Annuler</translation> +<translation id="795667975304826397">Aucun fichier choisi</translation> +<translation id="1275511093094545429">Plug-in <ph name="PLUGIN"/> requis</translation> +<translation id="8662565117025751661">Téléchargement du plug-in...</translation> +<translation id="8141602879876242471">Vous pouvez lancer des recherches dans cet index. Pour cela, entrez des mots clés de recherche :</translation> +<translation id="6845533974506654842">appuyer</translation> +<translation id="8244226242650769279">image map</translation> +<translation id="1383141426028388991">Échec de l'installation du plugin depuis <ph name="URL"/></translation> +<translation id="2548326553472216322">Aucune recherche récente</translation> +<translation id="5944544982112848342">2048 (haute sécurité)</translation> +<translation id="3040011195152428237">Lien</translation> +<translation id="8281246460978372009">Après l'installation du plug-in, cliquez ici pour actualiser.</translation> +<translation id="7364796246159120393">Choisissez un fichier</translation> +<translation id="8964020114565522021">Placer le fichier ici</translation> +<translation id="838869780401515933">cocher</translation> +<translation id="2846343701378493991">1024 (sécurité moyenne)</translation> +<translation id="5476505524087279545">décocher</translation> +<translation id="679352192834563463">Aucun plug-in disponible pour afficher ce contenu</translation> +<translation id="3789841737615482174">Installer</translation> +<translation id="6663448176199120256">Recherches récentes</translation> +<translation id="3600343118165084788">Cliquez ici pour télécharger le plug-in</translation> +<translation id="6807599807928161586">Zone Web</translation> +<translation id="5939518447894949180">Réinitialiser</translation> +<translation id="3771786644471114952">Ajouter le plug-in</translation> +<translation id="1842960171412779397">sélectionner</translation> +<translation id="6119846243427417423">activer</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> fichiers</translation> +<translation id="3926627843712816530">Veuillez confirmer que vous souhaitez installer ce plug-in. Vous devez uniquement installer des plug-ins approuvés.</translation> +<translation id="4838490908464673667">Le plug-in requis n'est pas installé.</translation> +<translation id="8597182159515967513">en-tête</translation> +<translation id="2653659639078652383">Valider</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_gu.xtb b/webkit/glue/resources/webkit_strings_gu.xtb new file mode 100644 index 0000000..fb40c72 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_gu.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="gu"> +<translation id="4420062214988137980">પ્લગઇન ઇન્સ્ટોલેશન નિષ્ફળ થયું છે</translation> +<translation id="1235745349614807883">હાલની શોધને સાફ કરો</translation> +<translation id="3825324228893189080">વધારાના પ્લગઇનની જરૂરિયાત</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> પ્લગઇન ઇન્સ્ટોલ કરેલું નથી</translation> +<translation id="5048533449481078685">સૂચિ માર્કર</translation> +<translation id="4202807286478387388">જંપ કરો</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">કૃપા કરીને પુષ્ટિ કરો કે તમને <ph name="PLUGIN"/> પ્લગઇન ઇન્સ્ટોલ કરવું ગમશે. તમારે ફક્ત તે જ પ્લગઇન્સ ઇન્સ્ટોલ કરવા જોઈએ જેના પર તમે વિશ્વાસ કરો છો.</translation> +<translation id="7658239707568436148">રદ કરો</translation> +<translation id="795667975304826397">કોઈ ફાઇલ પસંદ કરેલી નથી</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> પ્લગઇનની જરૂરિયાત છે</translation> +<translation id="8662565117025751661">પ્લગઇન ડાઉનલોડ કરી રહ્યું છે...</translation> +<translation id="8141602879876242471">આ એક શોધસક્ષમ અનુક્રમણિકા છે. શોધ કીવર્ડ્સ દાખલ કરો:</translation> +<translation id="6845533974506654842">દબાવો</translation> +<translation id="8244226242650769279">છબી નકશો</translation> +<translation id="1383141426028388991"><ph name="URL"/> માંથી પ્લગઇન ઇન્સ્ટોલ કરવામાં નિષ્ફળ</translation> +<translation id="2548326553472216322">હાલની શોધો નથી</translation> +<translation id="5944544982112848342">2048 (ઉચ્ચ ગ્રેડ)</translation> +<translation id="3040011195152428237">લિંક</translation> +<translation id="8281246460978372009">પ્લગઇન ઇન્સ્ટોલ કર્યા પછી, રીફ્રેશ કરવા અહીં ક્લિક કરો</translation> +<translation id="7364796246159120393">ફાઇલ પસંદ કરો</translation> +<translation id="8964020114565522021">ફાઇલને અહીં ખેંચો</translation> +<translation id="838869780401515933">તપાસો</translation> +<translation id="2846343701378493991">1024 (મધ્યમ ગ્રેડ)</translation> +<translation id="5476505524087279545">અનચેક કરો</translation> +<translation id="679352192834563463">આ સમાગ્રી પ્રદર્શિત કરવા માટે કોઈ પ્લગઇન ઉપલબ્ધ નથી</translation> +<translation id="3789841737615482174">ઇન્સ્ટોલ કરો</translation> +<translation id="6663448176199120256">તાજેતરની શોધ</translation> +<translation id="3600343118165084788">પ્લગઇન ડાઉનલોડ કરવા માટે અહીં ક્લિક કરો</translation> +<translation id="6807599807928161586">વેબ ક્ષેત્ર</translation> +<translation id="5939518447894949180">રીસેટ કરો</translation> +<translation id="3771786644471114952">પ્લગઇન મેળવો</translation> +<translation id="1842960171412779397">પસંદ કરો</translation> +<translation id="6119846243427417423">સક્રિય કરો</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ફાઇલો</translation> +<translation id="3926627843712816530">કૃપા કરીને પુષ્ટિ કરો કે તમને આ પ્લગઇન ઇન્સ્ટોલ કરવું ગમશે. તમારે ફક્ત તે જ પ્લગઇન્સ ઇન્સ્ટોલ કરવા જોઈએ જેના પર તમે વિશ્વાસ કરો છો.</translation> +<translation id="4838490908464673667">જોઈતું પ્લગઇન ઇન્સ્ટોલ કરેલું નથી</translation> +<translation id="8597182159515967513">મથાળું</translation> +<translation id="2653659639078652383">સબમિટ કરો</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_hi.xtb b/webkit/glue/resources/webkit_strings_hi.xtb new file mode 100644 index 0000000..8522437 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_hi.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hi"> +<translation id="4420062214988137980">प्लगइन स्थापना असफ़ल रही</translation> +<translation id="1235745349614807883">हाल ही की खोजें साफ़ करें</translation> +<translation id="3825324228893189080">अतिरिक्त प्लगइन आवश्यक</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> प्लगइन स्थापित नहीं किया गया है</translation> +<translation id="5048533449481078685">सूची चिन्हक</translation> +<translation id="4202807286478387388">जाएँ</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">कृपया पुष्टि करें कि आप <ph name="PLUGIN"/> प्लगइन स्थापित करना चाहते हैं. आपके केवल वे प्लगइन स्थापित करने चाहिए जिनपर आप भरोसा करते हों.</translation> +<translation id="7658239707568436148">रद्द करें</translation> +<translation id="795667975304826397">कोई फाइल नहीं चुनी गई</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> प्लगइन आवश्यक</translation> +<translation id="8662565117025751661">प्लगइन डाउनलोड कर रहा है ...</translation> +<translation id="8141602879876242471">यह एक खोजने योग्य इंडेक्स है. खोज कुंजीशब्द प्रविष्ट करें :</translation> +<translation id="6845533974506654842">दबाएँ</translation> +<translation id="8244226242650769279">चित्र मैप</translation> +<translation id="1383141426028388991"><ph name="URL"/> से प्लगइन इंस्टाल करने में असफ़ल रहा</translation> +<translation id="2548326553472216322">हाल ही में कोई खोज नहीं</translation> +<translation id="5944544982112848342">2048 (उच्च ग्रेड)</translation> +<translation id="3040011195152428237">लिंक</translation> +<translation id="8281246460978372009">प्लगइन स्थापित करने के बाद, पुन: ताज़ा करने के लिए यहाँ क्लिक करें</translation> +<translation id="7364796246159120393">फ़ाइल चुनें</translation> +<translation id="8964020114565522021">फाइल खींचकर यहाँ लाएं</translation> +<translation id="838869780401515933">चेक करें</translation> +<translation id="2846343701378493991">1024 (मध्यम ग्रेड)</translation> +<translation id="5476505524087279545">अनचेक करें</translation> +<translation id="679352192834563463">इस सामग्री को प्रदर्शित करने के लिए कोई प्लगइन उपलब्ध नहीं है</translation> +<translation id="3789841737615482174">स्थापित करें</translation> +<translation id="6663448176199120256">हाल ही में की गई खोजें</translation> +<translation id="3600343118165084788">प्लगइन डाउनलोड करने के लिए यहाँ क्लिक करें</translation> +<translation id="6807599807928161586">वेब क्षेत्र</translation> +<translation id="5939518447894949180">पुन: सेट करें</translation> +<translation id="3771786644471114952">प्लगइन प्राप्त करें</translation> +<translation id="1842960171412779397">चुनें</translation> +<translation id="6119846243427417423">सक्रिय करें</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> फ़ाइलें</translation> +<translation id="3926627843712816530">कृपया पुष्टि करें कि आप यह प्लगइन स्थापित करना चाहते हैं. आपके केवल वे प्लगइन स्थापित करने चाहिए जिनपर आप भरोसा करते हों.</translation> +<translation id="4838490908464673667">आवश्यक प्लगइन स्थापित नहीं है</translation> +<translation id="8597182159515967513">हेडिंग</translation> +<translation id="2653659639078652383">जमा करें</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_hr.xtb b/webkit/glue/resources/webkit_strings_hr.xtb new file mode 100644 index 0000000..bc93649 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_hr.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hr"> +<translation id="4420062214988137980">Nije uspjela instalacija dodatka</translation> +<translation id="1235745349614807883">Obriši najnovija pretraživanja</translation> +<translation id="3825324228893189080">Potreban je dodatak</translation> +<translation id="2965480764085142436">Nije instaliran dodatak za <ph name="PLUGIN"/></translation> +<translation id="5048533449481078685">oznaka popisa</translation> +<translation id="4202807286478387388">skoči</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Potvrdite da želite instalirati dodatak za <ph name="PLUGIN"/> Instalirajte samo one dodatke kojima vjerujete.</translation> +<translation id="7658239707568436148">Odustani</translation> +<translation id="795667975304826397">Nije odabrana niti jedna datoteka.</translation> +<translation id="1275511093094545429">Potreban je dodatak za <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Preuzimanje dodatka...</translation> +<translation id="8141602879876242471">Ovaj je indeks moguće pretraživati. Unesite ključne riječi za pretraživanje:</translation> +<translation id="6845533974506654842">pritisni</translation> +<translation id="8244226242650769279">karta slika</translation> +<translation id="1383141426028388991">Nije uspjela instalacija dodatka s <ph name="URL"/></translation> +<translation id="2548326553472216322">Nema najnovijih pretraživanja</translation> +<translation id="5944544982112848342">2048 (visoki stupanj)</translation> +<translation id="3040011195152428237">veza</translation> +<translation id="8281246460978372009">Nakon instalacije dodatka, kliknite ovdje za osvježenje stranice</translation> +<translation id="7364796246159120393">Odaberi datoteku</translation> +<translation id="8964020114565522021">Povucite datoteku ovamo</translation> +<translation id="838869780401515933">označi</translation> +<translation id="2846343701378493991">1024 (srednji)</translation> +<translation id="5476505524087279545">ukloni oznaku</translation> +<translation id="679352192834563463">Nema dostupnog dodatka za prikaz ovog sadržaja</translation> +<translation id="3789841737615482174">Instaliraj</translation> +<translation id="6663448176199120256">Najnovija pretraživanja</translation> +<translation id="3600343118165084788">Kliknite ovdje za preuzimanje dodatka</translation> +<translation id="6807599807928161586">web područje</translation> +<translation id="5939518447894949180">Ponovno postavi</translation> +<translation id="3771786644471114952">Dohvati dodatak</translation> +<translation id="1842960171412779397">odaberi</translation> +<translation id="6119846243427417423">aktiviraj</translation> +<translation id="8444882422881193423">Broj datoteka: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Potvrdite da želite instalirati ovaj dodatak. Instalirajte samo one dodatke kojima vjerujete.</translation> +<translation id="4838490908464673667">Potreban dodatak nije instaliran</translation> +<translation id="8597182159515967513">naslov</translation> +<translation id="2653659639078652383">Pošalji</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_hu.xtb b/webkit/glue/resources/webkit_strings_hu.xtb new file mode 100644 index 0000000..7a04612 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_hu.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hu"> +<translation id="4420062214988137980">A plugin telepítése nem sikerült</translation> +<translation id="1235745349614807883">Friss keresések törlése</translation> +<translation id="3825324228893189080">További plugin szükséges</translation> +<translation id="2965480764085142436">A(z) <ph name="PLUGIN"/> plugin nem lett telepítve</translation> +<translation id="5048533449481078685">listajelölő</translation> +<translation id="4202807286478387388">Mehet</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Kérjük, erősítse meg, hogy telepíteni szeretné a(z) <ph name="PLUGIN"/> plugint. Csak azokat a plugineket telepítse, amelyekben megbízik.</translation> +<translation id="7658239707568436148">Mégse</translation> +<translation id="795667975304826397">Nem lett fájl kiválasztva</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> plugin szükséges</translation> +<translation id="8662565117025751661">Plugin letöltése...</translation> +<translation id="8141602879876242471">Ez egy kereshető index. Írjon be keresési kulcsszavakat:</translation> +<translation id="6845533974506654842">Gomb lenyomása</translation> +<translation id="8244226242650769279">képtérkép</translation> +<translation id="1383141426028388991">A plugin telepítése nem sikerült a következő helyről: <ph name="URL"/></translation> +<translation id="2548326553472216322">Nincsenek friss keresések</translation> +<translation id="5944544982112848342">2048 (magasfokú)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">A plugin telepítését követően ide kattintson a frissítéshez</translation> +<translation id="7364796246159120393">Fájl kiválasztása</translation> +<translation id="8964020114565522021">Húzza át ide a fájlt</translation> +<translation id="838869780401515933">Megjelölés</translation> +<translation id="2846343701378493991">1024 (Közepes)</translation> +<translation id="5476505524087279545">Megjelölés eltávolítása</translation> +<translation id="679352192834563463">Nincs elérhető plugin a tartalom megjelenítéséhez</translation> +<translation id="3789841737615482174">Telepítés</translation> +<translation id="6663448176199120256">Friss keresések</translation> +<translation id="3600343118165084788">Ha ide kattint, letöltheti a plugint</translation> +<translation id="6807599807928161586">internetes terület</translation> +<translation id="5939518447894949180">Visszaállítás</translation> +<translation id="3771786644471114952">Plugin beszerzése</translation> +<translation id="1842960171412779397">Kiválasztás</translation> +<translation id="6119846243427417423">Aktiválás</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> fájl</translation> +<translation id="3926627843712816530">Kérjük, erősítse meg, hogy telepíteni szeretné ezt a plugint. Csak azokat a plugineket telepítse, amelyekben megbízik.</translation> +<translation id="4838490908464673667">A szükséges plugin nem lett telepítve</translation> +<translation id="8597182159515967513">fejléc</translation> +<translation id="2653659639078652383">Elküldés</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_id.xtb b/webkit/glue/resources/webkit_strings_id.xtb new file mode 100644 index 0000000..d174e84 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_id.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="id"> +<translation id="4420062214988137980">Penginstalan plug-in gagal</translation> +<translation id="1235745349614807883">Hapus Penelusuran Barusan</translation> +<translation id="3825324228893189080">Diperlukan plug-in tambahan</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> plug-in belum terinstal</translation> +<translation id="5048533449481078685">penanda daftar</translation> +<translation id="4202807286478387388">lompati</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/> - <ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Konfirmasikan bahwa Anda ingin menginstal plug-in<ph name="PLUGIN"/>. Sebaiknya hanya instal plug-in yang Anda percaya.</translation> +<translation id="7658239707568436148">Batal</translation> +<translation id="795667975304826397">Tidak ada file yang dipilih</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> diperlukan plug-in</translation> +<translation id="8662565117025751661">Mengunduh plug-in...</translation> +<translation id="8141602879876242471">Terdapat indeks yang dapat dicari. Masukkan kata kunci penelusuran:</translation> +<translation id="6845533974506654842">tekan</translation> +<translation id="8244226242650769279">gambar peta</translation> +<translation id="1383141426028388991">Gagal menginstal plug-in dari <ph name="URL"/></translation> +<translation id="2548326553472216322">Tidak ada penelusuran terkini</translation> +<translation id="5944544982112848342">2048 (Tingkat Tinggi)</translation> +<translation id="3040011195152428237">tautan</translation> +<translation id="8281246460978372009">Setelah menginstal plug-in, klik di sini untuk me-refresh</translation> +<translation id="7364796246159120393">Pilih Berkas</translation> +<translation id="8964020114565522021">Tarik file ke sini</translation> +<translation id="838869780401515933">centangi</translation> +<translation id="2846343701378493991">1024 (Tingkat Menengah)</translation> +<translation id="5476505524087279545">batalkan centang</translation> +<translation id="679352192834563463">Tidak tersedia plug-in untuk menampilkan konten ini</translation> +<translation id="3789841737615482174">Instal</translation> +<translation id="6663448176199120256">Penelusuran Barusan</translation> +<translation id="3600343118165084788">Klik di sini untuk mengunduh plug-in</translation> +<translation id="6807599807928161586">area Web</translation> +<translation id="5939518447894949180">Atur ulang</translation> +<translation id="3771786644471114952">Dapatkan Plug-in</translation> +<translation id="1842960171412779397">pilih</translation> +<translation id="6119846243427417423">aktifkan</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> berkas</translation> +<translation id="3926627843712816530">Konfirmasikan bahwa Anda ingin menginstal plug-in ini. Sebaiknya hanya instal plug-in yang Anda percaya.</translation> +<translation id="4838490908464673667">Plug-in yang diperlukan belum terinstal</translation> +<translation id="8597182159515967513">kepala</translation> +<translation id="2653659639078652383">Kirim</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_it.xtb b/webkit/glue/resources/webkit_strings_it.xtb new file mode 100644 index 0000000..f599db0 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_it.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="it"> +<translation id="4420062214988137980">Installazione plug-in non riuscita</translation> +<translation id="1235745349614807883">Cancella ricerche recenti</translation> +<translation id="3825324228893189080">Plug-in aggiuntivo necessario</translation> +<translation id="2965480764085142436">Plug-in <ph name="PLUGIN"/> non installato</translation> +<translation id="5048533449481078685">indicatore elenco</translation> +<translation id="4202807286478387388">vai</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Conferma l'installazione del plug-in <ph name="PLUGIN"/>. Ti invitiamo a installare solo i plug-in che ritieni affidabili.</translation> +<translation id="7658239707568436148">Annulla</translation> +<translation id="795667975304826397">Nessun file selezionato</translation> +<translation id="1275511093094545429">Plug-in <ph name="PLUGIN"/> necessario</translation> +<translation id="8662565117025751661">Download del plug-in in corso...</translation> +<translation id="8141602879876242471">Questo è un indice di ricerca. Inserisci le parole chiave di ricerca:</translation> +<translation id="6845533974506654842">premi</translation> +<translation id="8244226242650769279">image map</translation> +<translation id="1383141426028388991">Installazione plug-in da <ph name="URL"/> non riuscita</translation> +<translation id="2548326553472216322">Nessuna ricerca recente</translation> +<translation id="5944544982112848342">2048 (alta qualità)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Dopo aver installato il plug-in, fai clic qui per aggiornare</translation> +<translation id="7364796246159120393">Scegli file</translation> +<translation id="8964020114565522021">Trascina il file qui</translation> +<translation id="838869780401515933">seleziona</translation> +<translation id="2846343701378493991">1024 (Medium Grade)</translation> +<translation id="5476505524087279545">deseleziona</translation> +<translation id="679352192834563463">Nessun plug-in disponibile per visualizzare il contenuto</translation> +<translation id="3789841737615482174">Installa</translation> +<translation id="6663448176199120256">Ricerche recenti</translation> +<translation id="3600343118165084788">Fai clic qui per scaricare il plug-in</translation> +<translation id="6807599807928161586">area web</translation> +<translation id="5939518447894949180">Ripristina</translation> +<translation id="3771786644471114952">Aggiungi plug-in</translation> +<translation id="1842960171412779397">seleziona</translation> +<translation id="6119846243427417423">attiva</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> file</translation> +<translation id="3926627843712816530">Conferma l'installazione del plug-in. Ti invitiamo a installare solo i plug-in che ritieni affidabili.</translation> +<translation id="4838490908464673667">Il plug-in richiesto non è installato</translation> +<translation id="8597182159515967513">intestazione</translation> +<translation id="2653659639078652383">Invia</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_iw.xtb b/webkit/glue/resources/webkit_strings_iw.xtb new file mode 100644 index 0000000..652d600 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_iw.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="iw"> +<translation id="4420062214988137980">התקנת פלגא-אין נכשלה</translation> +<translation id="1235745349614807883">הסר חיפושים אחרונים</translation> +<translation id="3825324228893189080">דרוש תוסף נוסף</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> אינו מותקן</translation> +<translation id="5048533449481078685">סמן רשימה</translation> +<translation id="4202807286478387388">קפוץ</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">אשר שברצונך להתקין את התוסף <ph name="PLUGIN"/>. עליך להתקין רק תוספים אתה בוטח.</translation> +<translation id="7658239707568436148">ביטול</translation> +<translation id="795667975304826397">לא נבחר קובץ</translation> +<translation id="1275511093094545429">נדרש plugin <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">מוריד תוסף...</translation> +<translation id="8141602879876242471">זהו אינדקס שניתן לבצע בו חיפוש. הזן מילות מפתח לחיפוש:</translation> +<translation id="6845533974506654842">לחץ</translation> +<translation id="8244226242650769279">מפת תמונות</translation> +<translation id="1383141426028388991">התקנת התוסף (plugin) מ-<ph name="URL"/> נכשלה</translation> +<translation id="2548326553472216322">אין חיפושים אחרונים</translation> +<translation id="5944544982112848342">2048 (High Grade)</translation> +<translation id="3040011195152428237">קישור</translation> +<translation id="8281246460978372009">לאחר התקנת התוסף, לחץ כאן לרענון</translation> +<translation id="7364796246159120393">בחר קובץ</translation> +<translation id="8964020114565522021">גרור את הקובץ לכאן</translation> +<translation id="838869780401515933">סמן</translation> +<translation id="2846343701378493991">1024 (Medium Grade)</translation> +<translation id="5476505524087279545">בטל סימון</translation> +<translation id="679352192834563463">אין תוסף זמין להצגת תוכן זה</translation> +<translation id="3789841737615482174">התקן</translation> +<translation id="6663448176199120256">חיפושים אחרונים</translation> +<translation id="3600343118165084788">לחץ כאן להורדת התוסף</translation> +<translation id="6807599807928161586">אזור אינטרנט</translation> +<translation id="5939518447894949180">אפס</translation> +<translation id="3771786644471114952">קבל תוסף</translation> +<translation id="1842960171412779397">בחר</translation> +<translation id="6119846243427417423">הפעל</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> קבצים</translation> +<translation id="3926627843712816530">אנא אשר שברצונך להתקין תוסף זה. עליך להתקין רק תוספים שבהם אתה בוטח.</translation> +<translation id="4838490908464673667">התוסף המבוקש אינו מותקן</translation> +<translation id="8597182159515967513">כותרת</translation> +<translation id="2653659639078652383">שלח</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ja.xtb b/webkit/glue/resources/webkit_strings_ja.xtb new file mode 100644 index 0000000..c16df86 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ja.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ja"> +<translation id="4420062214988137980">プラグインのインストールが失敗しました</translation> +<translation id="1235745349614807883">最近の検索履歴を消去</translation> +<translation id="3825324228893189080">追加の必要なプラグイン</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> プラグインはインストールされていません</translation> +<translation id="5048533449481078685">リスト マーカー</translation> +<translation id="4202807286478387388">ジャンプ</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143"><ph name="PLUGIN"/> プラグインのインストールを確認してください。信頼できるプラグインだけインストールしてください。</translation> +<translation id="7658239707568436148">キャンセル</translation> +<translation id="795667975304826397">選択されていません</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> プラグインが必要です</translation> +<translation id="8662565117025751661">プラグインをダウンロードしています...</translation> +<translation id="8141602879876242471">このインデックスは検索できます。キーワードを入力してください:</translation> +<translation id="6845533974506654842">押す</translation> +<translation id="8244226242650769279">イメージ マップ</translation> +<translation id="1383141426028388991"><ph name="URL"/> からプラグインをインストールできませんでした</translation> +<translation id="2548326553472216322">最近の検索はありません</translation> +<translation id="5944544982112848342">2048 (高)</translation> +<translation id="3040011195152428237">リンク</translation> +<translation id="8281246460978372009">プラグインをインストールした後は、ここをクリックして更新してください</translation> +<translation id="7364796246159120393">ファイルを選択</translation> +<translation id="8964020114565522021">ファイルをここにドラッグ</translation> +<translation id="838869780401515933">チェックを付ける</translation> +<translation id="2846343701378493991">1024 (中)</translation> +<translation id="5476505524087279545">チェックを外す</translation> +<translation id="679352192834563463">このコンテンツの表示に使用できるプラグインはありません</translation> +<translation id="3789841737615482174">インストール</translation> +<translation id="6663448176199120256">最近の検索</translation> +<translation id="3600343118165084788">ここをクリックしてプラグインをダウンロード</translation> +<translation id="6807599807928161586">ウェブ領域</translation> +<translation id="5939518447894949180">リセット</translation> +<translation id="3771786644471114952">プラグインの取得</translation> +<translation id="1842960171412779397">選択</translation> +<translation id="6119846243427417423">アクティブにする</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ファイル</translation> +<translation id="3926627843712816530">このプラグインのインストールを確認してください。信頼できるプラグインだけインストールしてください。</translation> +<translation id="4838490908464673667">必要なプラグインがインストールされていません</translation> +<translation id="8597182159515967513">見出し</translation> +<translation id="2653659639078652383">送信</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_kn.xtb b/webkit/glue/resources/webkit_strings_kn.xtb new file mode 100644 index 0000000..1dacc42 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_kn.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="kn"> +<translation id="4420062214988137980">ಪ್ಲಗ್ಇನ್ ಇನ್ಸ್ಟಾಲೇಶನ್ ವಿಫಲವಾಗಿದೆ</translation> +<translation id="1235745349614807883">ಇತ್ತೀಚಿನ ಹುಡುಕಾಟವನ್ನು ತೆರವುಗೊಳಿಸಿ</translation> +<translation id="3825324228893189080">ಹೆಚ್ಚುವರಿ ಪ್ಲಗ್ಇನ್ಗಳ ಅವಶ್ಯಕತೆಯಿದೆ</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> ಪ್ಲಗ್ಇನ್ ಇನ್ಸ್ಟಾಲ್ ಆಗಿಲ್ಲ</translation> +<translation id="5048533449481078685">ಪಟ್ಟಿ ಗುರುತು</translation> +<translation id="4202807286478387388">ಹಾರು</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">ದಯವಿಟ್ಟು <ph name="PLUGIN"/> ಪ್ಲಗ್ಇನ್ ಅನ್ನು ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ಬಯಸುತ್ತೀರಾ ಎಂದು ಖಚಿತಪಡಿಸಿ. ನೀವು ನಂಬಿರುವ ಪ್ಲಗ್ಇನ್ಸ್ ಅನ್ನು ಮಾತ್ರ ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಬೇಕು.</translation> +<translation id="7658239707568436148">ರದ್ದುಮಾಡು</translation> +<translation id="795667975304826397">ಯಾವುದೇ ಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿಲ್ಲ</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> ಪ್ಲಗ್ಇನ್ ಅವಶ್ಯಕತೆಯಿದೆ</translation> +<translation id="8662565117025751661">ಡೌನ್ಲೋಡ್ ಆಗುತ್ತಿರುವ ಪ್ಲಗ್ ಇನ್...</translation> +<translation id="8141602879876242471">ಇದು ಹುಡುಕಾಡಬಹುದಾದ ಸೂಚಿಕೆ ಹುಡುಕಾಟ ಕೀವರ್ಡ್ಗಳನ್ನು ನಮೂದಿಸಿ:</translation> +<translation id="6845533974506654842">ಒತ್ತಿ</translation> +<translation id="8244226242650769279">ಇಮೇಜ್ ನಕ್ಷೆ</translation> +<translation id="1383141426028388991"><ph name="URL"/> ನಿಂದ ಪ್ಲಗ್ಇನ್ ಅನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ</translation> +<translation id="2548326553472216322">ಇತ್ತೀಚಿನ ಹುಡುಕಾಟಗಳು ಇಲ್ಲ</translation> +<translation id="5944544982112848342">2048 (ಉನ್ನತ ಶ್ರೇಣಿ)</translation> +<translation id="3040011195152428237">ಲಿಂಕ್</translation> +<translation id="8281246460978372009">ಪ್ಲಗ್ಇನ್ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿದ ನಂತರ, ತಾಜಾಮಾಡಲು ಇಲ್ಲಿ ಕ್ಲಿಕ್ ಮಾಡಿ</translation> +<translation id="7364796246159120393">ಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿ</translation> +<translation id="8964020114565522021">ಇಲ್ಲಿ ಫೈಲ್ ಅನ್ನು ಎಳೆಯಿರಿ</translation> +<translation id="838869780401515933">ಪರಿಶೀಲಿಸು</translation> +<translation id="2846343701378493991">1024 (ಮದ್ಯಮ ಶ್ರೇಣಿ)</translation> +<translation id="5476505524087279545">ಪರೀಕ್ಷಿಸಬೇಡಿ</translation> +<translation id="679352192834563463">ಈ ವಿಷಯವನ್ನು ಪ್ರದರ್ಶಿಸಲು ಯಾವುದೇ ಪ್ಲಗ್ಇನ್ ಲಭ್ಯವಿಲ್ಲ</translation> +<translation id="3789841737615482174">ಇನ್ಸ್ಟಾಲ್</translation> +<translation id="6663448176199120256">ಇತ್ತೀಚಿನ ಹುಡುಕಾಟಗಳು</translation> +<translation id="3600343118165084788">ಪ್ಲಗ್ಇನ್ ಅನ್ನು ಡೌನ್ಲೋಡ್ ಮಾಡಲು ಇಲ್ಲಿ ಕ್ಲಿಕ್ ಮಾಡಿ</translation> +<translation id="6807599807928161586">ವೆಬ್ ಪ್ರದೇಶ</translation> +<translation id="5939518447894949180">ಮರುಹೊಂದಿಸು</translation> +<translation id="3771786644471114952">ಪ್ಲಗ್ಇನ್ ಪಡೆಯಿರಿ</translation> +<translation id="1842960171412779397">ಆಯ್ಕೆ ಮಾಡಿ</translation> +<translation id="6119846243427417423">ಸಕ್ರಿಯಗೊಳಿಸು</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ಫೈಲ್ಗಳು</translation> +<translation id="3926627843712816530">ಈ ಪ್ಲಗ್ಇನ್ ಅನ್ನು ನೀವು ಇನ್ಸ್ಟಾಲ್ಮಾಡಲು ಬಯಸುತ್ತೀರಾ ಎಂದು ದಯವಿಟ್ಟು ಖಚಿತಪಡಿಸಿ. ನೀವು ನಂಬಿರುವ ಪ್ಲಗಿನ್ಸ್ ಅನ್ನು ಮಾತ್ರ ನೀವು ಇನ್ಸ್ಟಾಲ್ ಮಾಡಬೇಕು.</translation> +<translation id="4838490908464673667">ಅವಶ್ಯಕವಾಗಿರುವ ಪ್ಲಗ್ಇನ್ ಇನ್ಸ್ಟಾಲ್ ಆಗಿಲ್ಲ</translation> +<translation id="8597182159515967513">ಶೀರ್ಷಿಕೆ</translation> +<translation id="2653659639078652383">ಸಲ್ಲಿಸು</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ko.xtb b/webkit/glue/resources/webkit_strings_ko.xtb new file mode 100644 index 0000000..c3b3e3e --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ko.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ko"> +<translation id="4420062214988137980">플러그인 설치 실패</translation> +<translation id="1235745349614807883">최근 검색 삭제</translation> +<translation id="3825324228893189080">추가 플러그인 필요</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> 플러그인이 설치되지 않음</translation> +<translation id="5048533449481078685">목록 표시기</translation> +<translation id="4202807286478387388">건너뛰기</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143"><ph name="PLUGIN"/> 플러그인을 설치할 것인지 확인해 주세요. 신뢰하는 플러그인만 설치해야 합니다.</translation> +<translation id="7658239707568436148">취소</translation> +<translation id="795667975304826397">선택된 파일 없음</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> 플러그인 필요</translation> +<translation id="8662565117025751661">플러그인 다운로드 중...</translation> +<translation id="8141602879876242471">이것은 검색 색인합니다. 검색 키워드 입력:</translation> +<translation id="6845533974506654842">누르기</translation> +<translation id="8244226242650769279">이미지 지도</translation> +<translation id="1383141426028388991"><ph name="URL"/>의 플러그인 설치 실패</translation> +<translation id="2548326553472216322">최근 수행된 검색 없음</translation> +<translation id="5944544982112848342">2048(높은 등급)</translation> +<translation id="3040011195152428237">링크</translation> +<translation id="8281246460978372009">플러그인 설치 후 여기를 클릭하여 새로고침</translation> +<translation id="7364796246159120393">파일 선택</translation> +<translation id="8964020114565522021">여기로 파일 드래그</translation> +<translation id="838869780401515933">선택</translation> +<translation id="2846343701378493991">1024(중간 등급)</translation> +<translation id="5476505524087279545">선택취소</translation> +<translation id="679352192834563463">이 콘텐츠를 표시하는 데 사용할 플러그인 없음</translation> +<translation id="3789841737615482174">설치</translation> +<translation id="6663448176199120256">최근 수행된 검색</translation> +<translation id="3600343118165084788">여기를 클릭하여 플러그인을 다운로드합니다.</translation> +<translation id="6807599807928161586">웹 영역</translation> +<translation id="5939518447894949180">재설정</translation> +<translation id="3771786644471114952">플러그인 가져오기</translation> +<translation id="1842960171412779397">선택</translation> +<translation id="6119846243427417423">활성화</translation> +<translation id="8444882422881193423">파일 <ph name="NUMBER_OF_FILES"/>개</translation> +<translation id="3926627843712816530">해당 플러그인을 설치할 것인지 확인해 주세요. 신뢰하는 플러그인만 설치해야 합니다.</translation> +<translation id="4838490908464673667">필수 플러그인이 설치되지 않음</translation> +<translation id="8597182159515967513">항목</translation> +<translation id="2653659639078652383">제출</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_lt.xtb b/webkit/glue/resources/webkit_strings_lt.xtb new file mode 100644 index 0000000..9975381 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_lt.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lt"> +<translation id="4420062214988137980">Papildinio įdiegti nepavyko</translation> +<translation id="1235745349614807883">Išvalyti pastarąsias paieškas</translation> +<translation id="3825324228893189080">Reikia papidomo papildinio</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> papildinys neįdiegtas</translation> +<translation id="5048533449481078685">sąrašo žymeklis</translation> +<translation id="4202807286478387388">peršokti</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Patvirtinkite, kad norite diegti <ph name="PLUGIN"/> papildinį. Diekite tik tuos papildinius, kuriais tikrai pasitikite.</translation> +<translation id="7658239707568436148">Atšaukti</translation> +<translation id="795667975304826397">Nepasirinktas joks failas</translation> +<translation id="1275511093094545429">Reikia <ph name="PLUGIN"/> papildinio (-ių)</translation> +<translation id="8662565117025751661">Siunčiamas papildinys...</translation> +<translation id="8141602879876242471">Tai yra ieškotinas indeksas. Įveskite paieškos raktinių žodžių:</translation> +<translation id="6845533974506654842">paspausti</translation> +<translation id="8244226242650769279">paveikslėlio žemėlapis</translation> +<translation id="1383141426028388991">Nepavyko įdiegti papildinio iš <ph name="URL"/></translation> +<translation id="2548326553472216322">Pastaruoju metu paieškų nevykdyta</translation> +<translation id="5944544982112848342">2048 (Aukšto laipsnio)</translation> +<translation id="3040011195152428237">nuoroda</translation> +<translation id="8281246460978372009">Įdiegę papildinį paspauskite čia, kad būtų perkrautas puslapis</translation> +<translation id="7364796246159120393">Pasirinkti failą</translation> +<translation id="8964020114565522021">Vilkite failą čia</translation> +<translation id="838869780401515933">tikrinti</translation> +<translation id="2846343701378493991">1024 (vidutinio lygio)</translation> +<translation id="5476505524087279545">Nuimti žymėjimą</translation> +<translation id="679352192834563463">Nėra papildinio, galinčio parodyti šį turinį</translation> +<translation id="3789841737615482174">Diegti</translation> +<translation id="6663448176199120256">Naujausios paieškos</translation> +<translation id="3600343118165084788">Spustelėkite čia, kad atsisiųstumėte papildinį</translation> +<translation id="6807599807928161586">interneto sritis</translation> +<translation id="5939518447894949180">Nustatyti iš naujo</translation> +<translation id="3771786644471114952">Siųsti papildinį</translation> +<translation id="1842960171412779397">pasirinkti</translation> +<translation id="6119846243427417423">aktyvinti</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> failai (-ų)</translation> +<translation id="3926627843712816530">Patvirtinkite, kad norite diegti šį papildinį. Diekite tik tuos papildinius, kuriais tikrai pasitikite.</translation> +<translation id="4838490908464673667">Neįdiegtas reikalingas papildinys</translation> +<translation id="8597182159515967513">antraštė</translation> +<translation id="2653659639078652383">Pateikti</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_lv.xtb b/webkit/glue/resources/webkit_strings_lv.xtb new file mode 100644 index 0000000..6b7dfd5 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_lv.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lv"> +<translation id="4420062214988137980">Spraudņa iestatīšana neizdevās</translation> +<translation id="1235745349614807883">Dzēst nesenos meklējumus</translation> +<translation id="3825324228893189080">Nepieciešams papildus spraudnis</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> spraudnis nav iestatīts</translation> +<translation id="5048533449481078685">sarakstu marķieris</translation> +<translation id="4202807286478387388">lekt</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Lūdzu, apstipriniet, ka vēlaties iestatīt <ph name="PLUGIN"/> spraudni. Jums vajadzētu iestatīt tikai tos spraudņus, kuriem uzticaties.</translation> +<translation id="7658239707568436148">Atcelt</translation> +<translation id="795667975304826397">Nav izvēlēts neviens fails</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> nepieciešams spraudnis</translation> +<translation id="8662565117025751661">Spraudņu lejupielāde...</translation> +<translation id="8141602879876242471">Šis ir indekss ar meklēšanas iespējām. Ievadīt meklēšanas atslēgvārdus:</translation> +<translation id="6845533974506654842">nospiest</translation> +<translation id="8244226242650769279">attēlu karte</translation> +<translation id="1383141426028388991">Neizdevās instalēt spraudni no <ph name="URL"/></translation> +<translation id="2548326553472216322">Nav nesenu meklējumu</translation> +<translation id="5944544982112848342">2048 (Augsta Atzīme)</translation> +<translation id="3040011195152428237">saite</translation> +<translation id="8281246460978372009">Pēc spraudņa iestatīšanas, nospiediet šeit, lai atsvaidzinātu</translation> +<translation id="7364796246159120393">Izvēlieties failu</translation> +<translation id="8964020114565522021">Ievelciet failu šeit</translation> +<translation id="838869780401515933">prbaudt</translation> +<translation id="2846343701378493991">1024 (Vidēja Atzīme)</translation> +<translation id="5476505524087279545">neprbaudt</translation> +<translation id="679352192834563463">Nav pieejamu spraudņu, lai attēlotu saturu</translation> +<translation id="3789841737615482174">Iestatīt</translation> +<translation id="6663448176199120256">Neseni meklējumi</translation> +<translation id="3600343118165084788">Klikšķiniet šeit, lai lejupielādētu spraudni</translation> +<translation id="6807599807928161586">tīmekļa apgabals</translation> +<translation id="5939518447894949180">Atiestatīt</translation> +<translation id="3771786644471114952">Iegūt spraudni</translation> +<translation id="1842960171412779397">Atlasiet</translation> +<translation id="6119846243427417423">aktivizt</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> faili</translation> +<translation id="3926627843712816530">Lūdzu, apstipriniet, ka vēlaties iestatīt šo spraudni. Jums vajadzētu iestatīt tikai tos spraudņus, kuriem uzticaties.</translation> +<translation id="4838490908464673667">Nepieciešamais spraudnis nav iestatīts</translation> +<translation id="8597182159515967513">Virsraksts</translation> +<translation id="2653659639078652383">Iesniegt</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ml.xtb b/webkit/glue/resources/webkit_strings_ml.xtb new file mode 100644 index 0000000..d7db730 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ml.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ml"> +<translation id="4420062214988137980">പ്ലഗിന് ഇന്സ്റ്റൊളേഷന് പരാജയപ്പെട്ടു</translation> +<translation id="1235745349614807883">അടുത്തിടെയുള്ള തിരയലുകള് മായ്ക്കുക</translation> +<translation id="3825324228893189080">കൂടുതല് പ്ലഗിന് ആവശ്യമാണ്</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> പ്ലഗിന് ഇന്സ്റ്റോള് ചെയ്തില്ല</translation> +<translation id="5048533449481078685">പട്ടിക മാര്ക്കര്</translation> +<translation id="4202807286478387388">jump</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143"><ph name="PLUGIN"/> പ്ലഗിന് ഇന്സ്റ്റാള് ചെയ്യാന് നിങ്ങള് താല്പര്യപ്പെടുന്നുവെന്ന് ദയവായി ഉറപ്പുവരുത്തുക. നിങ്ങള്ക്ക് വിശ്വാസ്യതയുള്ള പ്ലഗിനുകള് മാത്രം നിങ്ങള് ഇന്സ്റ്റാള് ചെയ്യുക.</translation> +<translation id="7658239707568436148">റദ്ദാക്കുക</translation> +<translation id="795667975304826397">ഒരു ഫയലും തിരഞ്ഞെടുത്തിട്ടില്ല</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> പ്ലഗിന് ആവശ്യമുണ്ട്</translation> +<translation id="8662565117025751661">പ്ലഗ് ഇന് ഡൌണ്ലോഡ് ചെയ്യുകയാണ്...</translation> +<translation id="8141602879876242471">ഇത് തിരയാവുന്ന സൂചികയാണ്. തിരയല് കീവേഡുകള് നല്കുക:</translation> +<translation id="6845533974506654842">അമര്ത്തുക</translation> +<translation id="8244226242650769279">ഇമേജ് മാപ്പ്</translation> +<translation id="1383141426028388991"><ph name="URL"/> ല് നിന്ന് പ്ലഗ്-ഇന് ഇന്സ്റ്റോള് ചെയ്യുന്നതില് പരാജയപ്പെട്ടു</translation> +<translation id="2548326553472216322">സമീപകാല തിരയലുകള് ഇല്ല</translation> +<translation id="5944544982112848342">2048 (High Grade)</translation> +<translation id="3040011195152428237">ലിങ്ക്</translation> +<translation id="8281246460978372009">പ്ലഗിന് ഇന്സ്റ്റാള് ചെയ്തതിന് ശേഷം, പുതുക്കുന്നതിനായി ഇവിടെ ക്ലിക്കുചെയ്യുക</translation> +<translation id="7364796246159120393">ഫയല് തിരഞ്ഞെടുക്കൂ</translation> +<translation id="8964020114565522021">ഇവിടെ ഫയല് ഇഴയ്ക്കുക</translation> +<translation id="838869780401515933">പരിശോധിക്കൂ</translation> +<translation id="2846343701378493991">1024 (മീഡിയം ഗ്രേഡ്)</translation> +<translation id="5476505524087279545">അണ്ചെക്ക് ചെയ്യുക</translation> +<translation id="679352192834563463">ഈ ഉള്ളടക്കം പ്രദര്ശിപ്പിക്കുന്നതിന് ഒരു പ്ലഗിനും ലഭ്യമല്ല</translation> +<translation id="3789841737615482174">ഇന്സ്റ്റോള് ചെയ്യുക</translation> +<translation id="6663448176199120256">സമീപകാല തിരയലുകള്</translation> +<translation id="3600343118165084788">പ്ലഗ്-ഇന് ഡൌണ്ലോഡ് ചെയ്യുന്നതിനായി ഇവിടെ ക്ലിക്കുചെയ്യുക</translation> +<translation id="6807599807928161586">വെബ് മേഖല</translation> +<translation id="5939518447894949180">വീണ്ടും സജ്ജീകരിക്കുക</translation> +<translation id="3771786644471114952">പ്ലഗിന് നേടുക</translation> +<translation id="1842960171412779397">തിരഞ്ഞെടുക്കൂ</translation> +<translation id="6119846243427417423">ആക്റ്റിവേറ്റ് ചെയ്യുക</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ഫയലുകള്</translation> +<translation id="3926627843712816530">പ്ലഗിന് ഇന്സ്റ്റോള് ചെയ്യാന് നിങ്ങള് താല്പര്യപ്പെടുന്നുവെന്ന് ദയവായി ഉറപ്പുവരുത്തുക. നിങ്ങള്ക്ക് വിശ്വാസ്യതയുള്ള പ്ലഗിനുകള് മാത്രം നിങ്ങള് ഇന്സ്റ്റോള് ചെയ്യുക.</translation> +<translation id="4838490908464673667">ആവശ്യമായ പ്ലഗിന് ഇന്സ്റ്റാള് ചെയ്തില്ല</translation> +<translation id="8597182159515967513">തലക്കെട്ട്</translation> +<translation id="2653659639078652383">സമര്പ്പിക്കൂ</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_mr.xtb b/webkit/glue/resources/webkit_strings_mr.xtb new file mode 100644 index 0000000..fd6c4a4 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_mr.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="mr"> +<translation id="4420062214988137980">प्लगइन स्थापना अयशस्वी</translation> +<translation id="1235745349614807883">अलीकडील शोध साफ करा</translation> +<translation id="3825324228893189080">अतिरिक्त प्लगइन आवश्यक</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> प्लगइन स्थापित नाही</translation> +<translation id="5048533449481078685">सूची चिन्हक</translation> +<translation id="4202807286478387388">जंप करा</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">कृपया आपण <ph name="PLUGIN"/> हे प्लगइन स्थापित करू इच्छिता याची पुष्टी करा. आपण केवळ आपल्याला विश्वास असलेले प्लगइन स्थापित केले पाहिजेत.</translation> +<translation id="7658239707568436148">रद्द करा</translation> +<translation id="795667975304826397">कोणतीही फाइल निवडलेली नाही</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> प्लगइन आवश्यक</translation> +<translation id="8662565117025751661">प्लगइन डाउनलोड करीत आहे...</translation> +<translation id="8141602879876242471">ही शोध घेण्यायोग्य अनुक्रमणिका आहे. शोध कीवर्ड प्रविष्ट करा:</translation> +<translation id="6845533974506654842">दाबा</translation> +<translation id="8244226242650769279">प्रतिमा नकाशा</translation> +<translation id="1383141426028388991"><ph name="URL"/> वरून प्लगइन स्थापित करण्यात अयशस्वी</translation> +<translation id="2548326553472216322">अलीकडील शोध नाहीत</translation> +<translation id="5944544982112848342">2048 (उच्च ग्रेड)</translation> +<translation id="3040011195152428237">दुवा</translation> +<translation id="8281246460978372009">प्लगइन स्थापित केल्यानंतर, रीफ्रेश करण्यासाठी येथे क्लिक करा</translation> +<translation id="7364796246159120393">फाइल निवडा</translation> +<translation id="8964020114565522021">फाइल येथे ड्रॅग करा</translation> +<translation id="838869780401515933">तपासा</translation> +<translation id="2846343701378493991">1024 (मध्यम प्रत)</translation> +<translation id="5476505524087279545">अनचेक</translation> +<translation id="679352192834563463">ही सामग्री प्रदर्शित करण्यासाठी कोणतेही प्लगइन उपलब्ध नाही</translation> +<translation id="3789841737615482174">स्थापना करा</translation> +<translation id="6663448176199120256">अलीकडील शोध</translation> +<translation id="3600343118165084788">प्लगइन डाउनलोड करण्यासाठी येथे क्लिक करा</translation> +<translation id="6807599807928161586">वेब क्षेत्र</translation> +<translation id="5939518447894949180">रीसेट करा</translation> +<translation id="3771786644471114952">प्लगइन मिळवा</translation> +<translation id="1842960171412779397">निवडा</translation> +<translation id="6119846243427417423">सक्रिय करा</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> फायली</translation> +<translation id="3926627843712816530">कृपया आपण हे प्लगइन स्थापित करू इच्छिता याची पुष्टी करा. आपण ज्यांचेवर विश्वास करता केवळ असेच प्लगइन स्थापित करावेत.</translation> +<translation id="4838490908464673667">आवश्यक प्लगइन स्थापित नाही</translation> +<translation id="8597182159515967513">शीर्षलेख</translation> +<translation id="2653659639078652383">सबमिट करा</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_nl.xtb b/webkit/glue/resources/webkit_strings_nl.xtb new file mode 100644 index 0000000..c9cea68 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_nl.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="nl"> +<translation id="4420062214988137980">Installatie van plug-in mislukt</translation> +<translation id="1235745349614807883">Recente zoekopdrachten wissen</translation> +<translation id="3825324228893189080">Aanvullende plug-in vereist</translation> +<translation id="2965480764085142436">De plug-in <ph name="PLUGIN"/> is niet geïnstalleerd</translation> +<translation id="5048533449481078685">lijstmarkering</translation> +<translation id="4202807286478387388">Gaan naar</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Bevestig dat u de plugin <ph name="PLUGIN"/> wilt installeren. Installeer alleen plug-ins die u vertrouwt.</translation> +<translation id="7658239707568436148">Annuleren</translation> +<translation id="795667975304826397">Geen bestand gekozen</translation> +<translation id="1275511093094545429">Plug-in <ph name="PLUGIN"/> vereist</translation> +<translation id="8662565117025751661">Plug-in downloaden...</translation> +<translation id="8141602879876242471">Dit is een doorzoekbare index. Geef zoekwoorden op:</translation> +<translation id="6845533974506654842">Indrukken</translation> +<translation id="8244226242650769279">image map</translation> +<translation id="1383141426028388991">Kan plug-in niet installeren van <ph name="URL"/></translation> +<translation id="2548326553472216322">Geen recente zoekopdrachten</translation> +<translation id="5944544982112848342">2048 (hoog niveau)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Klik hier na het installeren van de plug-in om te vernieuwen</translation> +<translation id="7364796246159120393">Bestand kiezen</translation> +<translation id="8964020114565522021">Sleep bestand hier naartoe</translation> +<translation id="838869780401515933">Selecteren</translation> +<translation id="2846343701378493991">1024 (gemiddeld niveau)</translation> +<translation id="5476505524087279545">Deselecteren</translation> +<translation id="679352192834563463">Er is geen plug-in beschikbaar om deze inhoud weer te geven</translation> +<translation id="3789841737615482174">Installeren</translation> +<translation id="6663448176199120256">Recente zoekopdrachten</translation> +<translation id="3600343118165084788">Klik hier om de plug-in te downloaden</translation> +<translation id="6807599807928161586">webgedeelte</translation> +<translation id="5939518447894949180">Herstellen</translation> +<translation id="3771786644471114952">Plug-in ophalen</translation> +<translation id="1842960171412779397">Selecteren</translation> +<translation id="6119846243427417423">Activeren</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> bestanden</translation> +<translation id="3926627843712816530">Bevestig dat u deze plug-in wilt installeren. Installeer alleen plug-ins die u vertrouwt.</translation> +<translation id="4838490908464673667">De vereiste plug-in is niet geïnstalleerd</translation> +<translation id="8597182159515967513">kop</translation> +<translation id="2653659639078652383">Verzenden</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_no.xtb b/webkit/glue/resources/webkit_strings_no.xtb new file mode 100644 index 0000000..f8f419a --- /dev/null +++ b/webkit/glue/resources/webkit_strings_no.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="no"> +<translation id="4420062214988137980">Installasjon av programtillegg mislyktes</translation> +<translation id="1235745349614807883">Fjern nylige søk</translation> +<translation id="3825324228893189080">Ytterligere programtillegg kreves</translation> +<translation id="2965480764085142436">Programtillegget <ph name="PLUGIN"/> er ikke installert</translation> +<translation id="5048533449481078685">listemarkør</translation> +<translation id="4202807286478387388">hopp</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Du må bekrefte om du vil installere programtillegget <ph name="PLUGIN"/>. Du bør bare installere programtillegg du stoler på.</translation> +<translation id="7658239707568436148">Avbryt</translation> +<translation id="795667975304826397">Ingen fil valgt</translation> +<translation id="1275511093094545429">Programtillegget <ph name="PLUGIN"/> kreves</translation> +<translation id="8662565117025751661">Laster ned programtillegg...</translation> +<translation id="8141602879876242471">Dette er en søkbar indeks. Angi søkeordene:</translation> +<translation id="6845533974506654842">trykk</translation> +<translation id="8244226242650769279">bildekart</translation> +<translation id="1383141426028388991">Kunne ikke installere programtillegget fra <ph name="URL"/></translation> +<translation id="2548326553472216322">Ingen nylige søk</translation> +<translation id="5944544982112848342">2048 (sterk)</translation> +<translation id="3040011195152428237">kobling</translation> +<translation id="8281246460978372009">Når programtillegget er installert, klikker du her for å oppdatere</translation> +<translation id="7364796246159120393">Velg fil</translation> +<translation id="8964020114565522021">Dra filen hit</translation> +<translation id="838869780401515933">merk av</translation> +<translation id="2846343701378493991">1024 (middels)</translation> +<translation id="5476505524087279545">fjern merke</translation> +<translation id="679352192834563463">Intet programtillegg tilgjengelig for å vise dette innholdet</translation> +<translation id="3789841737615482174">Installer</translation> +<translation id="6663448176199120256">Nylige søk</translation> +<translation id="3600343118165084788">Klikk her for å laste ned programtillegget</translation> +<translation id="6807599807928161586">nettområde</translation> +<translation id="5939518447894949180">Tilbakestill</translation> +<translation id="3771786644471114952">Få programtillegg</translation> +<translation id="1842960171412779397">velg</translation> +<translation id="6119846243427417423">aktiver</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> filer</translation> +<translation id="3926627843712816530">Du må bekrefte om du vil installere programtillegget. Du bør bare installere programtillegg du stoler på.</translation> +<translation id="4838490908464673667">Det nødvendige programtillegget er ikke installert</translation> +<translation id="8597182159515967513">overskrift</translation> +<translation id="2653659639078652383">Send</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_pl.xtb b/webkit/glue/resources/webkit_strings_pl.xtb new file mode 100644 index 0000000..c666eb9 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_pl.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pl"> +<translation id="4420062214988137980">Instalacja dodatku plug-in nie powiodła się</translation> +<translation id="1235745349614807883">Wyczyść ostatnie wyszukiwania</translation> +<translation id="3825324228893189080">Wymagany dodatkowy dodatek plug-in</translation> +<translation id="2965480764085142436">Dodatek plug-in <ph name="PLUGIN"/> nie został zainstalowany</translation> +<translation id="5048533449481078685">znacznik listy</translation> +<translation id="4202807286478387388">przejdź</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Potwierdź instalację dodatku plug-in <ph name="PLUGIN"/>. Należy instalować wyłącznie zaufane dodatki plug-in.</translation> +<translation id="7658239707568436148">Anuluj</translation> +<translation id="795667975304826397">Nie wybrano pliku</translation> +<translation id="1275511093094545429">Wymagany dodatek plug-in <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Pobieranie dodatku plug-in...</translation> +<translation id="8141602879876242471">Ten indeks można przeszukiwać. Wprowadź wyszukiwane słowa kluczowe:</translation> +<translation id="6845533974506654842">naciśnij</translation> +<translation id="8244226242650769279">mapa grafiki</translation> +<translation id="1383141426028388991">Nie można zainstalować wtyczki z adresu <ph name="URL"/></translation> +<translation id="2548326553472216322">Brak ostatnich wyszukiwań</translation> +<translation id="5944544982112848342">2048 (wysoki poziom)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Po zainstalowaniu dodatku plug-in kliknij tutaj, aby odświeżyć</translation> +<translation id="7364796246159120393">Wybierz plik</translation> +<translation id="8964020114565522021">Przeciągnij plik tutaj</translation> +<translation id="838869780401515933">zaznacz</translation> +<translation id="2846343701378493991">1024 (średni poziom)</translation> +<translation id="5476505524087279545">odznacz</translation> +<translation id="679352192834563463">Brak dostępnego dodatku plug-in umożliwiającego wyświetlenie tej treści</translation> +<translation id="3789841737615482174">Zainstaluj</translation> +<translation id="6663448176199120256">Ostatnie wyszukiwania</translation> +<translation id="3600343118165084788">Kliknij tutaj, aby pobrać dodatek plug-in</translation> +<translation id="6807599807928161586">obszar sieci</translation> +<translation id="5939518447894949180">Resetuj</translation> +<translation id="3771786644471114952">Pobierz dodatek plug-in</translation> +<translation id="1842960171412779397">wybierz</translation> +<translation id="6119846243427417423">aktywuj</translation> +<translation id="8444882422881193423">Liczba plików: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Potwierdź instalację tego dodatku plug-in. Należy instalować wyłącznie zaufane dodatki plug-in.</translation> +<translation id="4838490908464673667">Wymagany dodatek plug-in nie został zainstalowany</translation> +<translation id="8597182159515967513">nagłówek</translation> +<translation id="2653659639078652383">Prześlij</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_pt-BR.xtb b/webkit/glue/resources/webkit_strings_pt-BR.xtb new file mode 100644 index 0000000..5dfc2d3 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_pt-BR.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-BR"> +<translation id="4420062214988137980">Falha de instalação de plug-in</translation> +<translation id="1235745349614807883">Limpar pesquisas recentes</translation> +<translation id="3825324228893189080">Plug-in adicional necessário</translation> +<translation id="2965480764085142436">O plug-in <ph name="PLUGIN"/> não está instalado</translation> +<translation id="5048533449481078685">marcador de lista</translation> +<translation id="4202807286478387388">pular</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirme se deseja instalar este plug-in <ph name="PLUGIN"/>. Instale somente plug-ins confiáveis.</translation> +<translation id="7658239707568436148">Cancelar</translation> +<translation id="795667975304826397">Nenhum arquivo selecionado</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> plug-in necessário</translation> +<translation id="8662565117025751661">Fazendo download do plug-in...</translation> +<translation id="8141602879876242471">Este é um índice pesquisável. Insira palavras-chave de pesquisa:</translation> +<translation id="6845533974506654842">pressione</translation> +<translation id="8244226242650769279">mapa de imagens</translation> +<translation id="1383141426028388991">Falha ao instalar o plug-in de <ph name="URL"/></translation> +<translation id="2548326553472216322">Nenhuma pesquisa recente</translation> +<translation id="5944544982112848342">2048 (Nível alto)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Depois de instalar o plug-in, clique aqui para atualizar</translation> +<translation id="7364796246159120393">Escolher arquivo</translation> +<translation id="8964020114565522021">Arraste o arquivo até aquil</translation> +<translation id="838869780401515933">marcar</translation> +<translation id="2846343701378493991">1024 (Nível médio)</translation> +<translation id="5476505524087279545">desmarcar</translation> +<translation id="679352192834563463">Nenhum plug-in disponível para exibir este conteúdo</translation> +<translation id="3789841737615482174">Instalar</translation> +<translation id="6663448176199120256">Pesquisas recentes</translation> +<translation id="3600343118165084788">Clique aqui para fazer download do plug-in</translation> +<translation id="6807599807928161586">área da web</translation> +<translation id="5939518447894949180">Redefinir</translation> +<translation id="3771786644471114952">Obter plug-in</translation> +<translation id="1842960171412779397">selecione</translation> +<translation id="6119846243427417423">ativar</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> arquivos</translation> +<translation id="3926627843712816530">Confirme se deseja instalar este plug-in. Instale somente plug-ins confiáveis.</translation> +<translation id="4838490908464673667">O plug-in necessário não está instalado</translation> +<translation id="8597182159515967513">cabeçalho</translation> +<translation id="2653659639078652383">Enviar</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_pt-PT.xtb b/webkit/glue/resources/webkit_strings_pt-PT.xtb new file mode 100644 index 0000000..4b585e5 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_pt-PT.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-PT"> +<translation id="4420062214988137980">A instalação do plug-in falhou</translation> +<translation id="1235745349614807883">Limpar pesquisas recentes</translation> +<translation id="3825324228893189080">Plug-in adicional necessário</translation> +<translation id="2965480764085142436">O plug-in <ph name="PLUGIN"/> não está instalado</translation> +<translation id="5048533449481078685">marcador de lista</translation> +<translation id="4202807286478387388">ir para</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirme que pretende instalar o plug-in <ph name="PLUGIN"/>. Apenas deverá instalar plug-ins fidedignos.</translation> +<translation id="7658239707568436148">Cancelar</translation> +<translation id="795667975304826397">Nenhum ficheiro seleccionado</translation> +<translation id="1275511093094545429">Plug-in <ph name="PLUGIN"/> necessário</translation> +<translation id="8662565117025751661">A transferir o plug-in…</translation> +<translation id="8141602879876242471">Este índice é pesquisável. Introduza palavras-chave de pesquisa:</translation> +<translation id="6845533974506654842">premir</translation> +<translation id="8244226242650769279">mapa de imagem</translation> +<translation id="1383141426028388991">Falha ao instalar plug-in de <ph name="URL"/></translation> +<translation id="2548326553472216322">Nenhuma pesquisa recente</translation> +<translation id="5944544982112848342">2048 (Tamanho grande)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">Após instalar o plug-in, clique aqui para actualizar</translation> +<translation id="7364796246159120393">Escolher ficheiro</translation> +<translation id="8964020114565522021">Arraste o ficheiro para aqui</translation> +<translation id="838869780401515933">verificar</translation> +<translation id="2846343701378493991">1024 (Tamanho médio)</translation> +<translation id="5476505524087279545">desmarcar</translation> +<translation id="679352192834563463">Nenhum plug-in disponível para apresentar este conteúdo</translation> +<translation id="3789841737615482174">Instalar</translation> +<translation id="6663448176199120256">Pesquisas recentes</translation> +<translation id="3600343118165084788">Clique aqui para transferir o plug-in</translation> +<translation id="6807599807928161586">Área Web</translation> +<translation id="5939518447894949180">Repor</translation> +<translation id="3771786644471114952">Obter plug-in</translation> +<translation id="1842960171412779397">seleccionar</translation> +<translation id="6119846243427417423">activar</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ficheiros</translation> +<translation id="3926627843712816530">Confirme que pretende instalar este plug-in. Apenas deverá instalar plug-ins fidedignos.</translation> +<translation id="4838490908464673667">O plug-in necessário não está instalado</translation> +<translation id="8597182159515967513">cabeçalho</translation> +<translation id="2653659639078652383">Submeter</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ro.xtb b/webkit/glue/resources/webkit_strings_ro.xtb new file mode 100644 index 0000000..72c8f43 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ro.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ro"> +<translation id="4420062214988137980">Instalarea plug-in-ului a eşuat</translation> +<translation id="1235745349614807883">Ştergeţi căutările recente</translation> +<translation id="3825324228893189080">Este necesar un plug-in suplimentar</translation> +<translation id="2965480764085142436">Plug-in-ul <ph name="PLUGIN"/> nu este instalat</translation> +<translation id="5048533449481078685">marcator listă</translation> +<translation id="4202807286478387388">Salt</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Confirmaţi că doriţi să instalaţi plug-in-ul <ph name="PLUGIN"/>. Ar trebui să instalaţi numai plug-in-uri în care aveţi încredere.</translation> +<translation id="7658239707568436148">Anulaţi</translation> +<translation id="795667975304826397">Nu s-au ales fişiere</translation> +<translation id="1275511093094545429">Este necesar plug-in-ul <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Se descarcă plug-inul...</translation> +<translation id="8141602879876242471">Acesta este un index în care se poate căuta. Introduceţi cuvintele cheie pentru căutare:</translation> +<translation id="6845533974506654842">Apăsaţi</translation> +<translation id="8244226242650769279">hartă cu imagini</translation> +<translation id="1383141426028388991">Instalarea plug-in-ului de la <ph name="URL"/> a eşuat</translation> +<translation id="2548326553472216322">Nicio căutare recentă</translation> +<translation id="5944544982112848342">2048 (Grad înalt)</translation> +<translation id="3040011195152428237">link</translation> +<translation id="8281246460978372009">După instalarea plug-in-ului, faceţi clic aici pentru a reactualiza</translation> +<translation id="7364796246159120393">Alegeţi fişierul</translation> +<translation id="8964020114565522021">Trageţi fişierul aici</translation> +<translation id="838869780401515933">Bifaţi</translation> +<translation id="2846343701378493991">1024 (Grad mediu)</translation> +<translation id="5476505524087279545">Debifaţi</translation> +<translation id="679352192834563463">Niciun plug-in disponibil pentru a afişa acest conţinut</translation> +<translation id="3789841737615482174">Instalaţi</translation> +<translation id="6663448176199120256">Căutări recente</translation> +<translation id="3600343118165084788">Faceţi clic aici pentru a descărca plug-in-ul</translation> +<translation id="6807599807928161586">zona Web</translation> +<translation id="5939518447894949180">Resetaţi</translation> +<translation id="3771786644471114952">Obţineţi plug-in-ul</translation> +<translation id="1842960171412779397">Selectaţi</translation> +<translation id="6119846243427417423">Activaţi</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> fişiere</translation> +<translation id="3926627843712816530">Confirmaţi că doriţi să instalaţi acest plug-in. Ar trebui să instalaţi numai plug-in-uri în care aveţi încredere.</translation> +<translation id="4838490908464673667">Plug-in-ul necesar nu este instalat</translation> +<translation id="8597182159515967513">titlu</translation> +<translation id="2653659639078652383">Trimiteţi</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ru.xtb b/webkit/glue/resources/webkit_strings_ru.xtb new file mode 100644 index 0000000..b9fe2f3 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ru.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ru"> +<translation id="4420062214988137980">Ошибка при установке плагина</translation> +<translation id="1235745349614807883">Очистить недавние поиски</translation> +<translation id="3825324228893189080">Необходим дополнительный плагин</translation> +<translation id="2965480764085142436">Плагин <ph name="PLUGIN"/> не установлен</translation> +<translation id="5048533449481078685">маркер списка</translation> +<translation id="4202807286478387388">перейти</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Подтвердите, что хотите установить плагин <ph name="PLUGIN"/>. Устанавливать следует модули только тех издателей, которым вы доверяете.</translation> +<translation id="7658239707568436148">Отмена</translation> +<translation id="795667975304826397">Файл не выбран</translation> +<translation id="1275511093094545429">Необходим плагин <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Загрузка подключаемого модуля...</translation> +<translation id="8141602879876242471">Это индекс с возможностью поиска. Введите ключевые слова для поиска:</translation> +<translation id="6845533974506654842">нажать</translation> +<translation id="8244226242650769279">графическая карта</translation> +<translation id="1383141426028388991">Не удалось установить подключаемый модуль со следующего адреса: <ph name="URL"/></translation> +<translation id="2548326553472216322">Нет недавних поисков</translation> +<translation id="5944544982112848342">2048 (Крупный размер)</translation> +<translation id="3040011195152428237">ссылка</translation> +<translation id="8281246460978372009">После установки подключаемого модуля нажмите здесь для обновления</translation> +<translation id="7364796246159120393">Выберите файл</translation> +<translation id="8964020114565522021">Перетащите файл сюда</translation> +<translation id="838869780401515933">поставить галочку</translation> +<translation id="2846343701378493991">1024 (Средний размер)</translation> +<translation id="5476505524087279545">снять галочку</translation> +<translation id="679352192834563463">Недоступен плагин для отображения этого содержания</translation> +<translation id="3789841737615482174">Установить</translation> +<translation id="6663448176199120256">Недавние поиски</translation> +<translation id="3600343118165084788">Нажмите здесь, чтобы загрузить плагин</translation> +<translation id="6807599807928161586">область Интернетеа</translation> +<translation id="5939518447894949180">Изменить</translation> +<translation id="3771786644471114952">Получить плагин</translation> +<translation id="1842960171412779397">выбрать</translation> +<translation id="6119846243427417423">активировать</translation> +<translation id="8444882422881193423">Число файлов: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Подтвердите, что хотите установить этот плагин. Устанавливать следует модули только тех издателей, которым вы доверяете.</translation> +<translation id="4838490908464673667">Не установлен необходимый плагин.</translation> +<translation id="8597182159515967513">заголовок</translation> +<translation id="2653659639078652383">Отправить</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_sk.xtb b/webkit/glue/resources/webkit_strings_sk.xtb new file mode 100644 index 0000000..0804eff --- /dev/null +++ b/webkit/glue/resources/webkit_strings_sk.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sk"> +<translation id="4420062214988137980">Nedal sa nainštalovať doplnkový modul</translation> +<translation id="1235745349614807883">Vyčistiť posledné vyhľadávania</translation> +<translation id="3825324228893189080">Vyžaduje sa ďalší doplnkový modul</translation> +<translation id="2965480764085142436">Nie je nainštalovaný doplnkový modul <ph name="PLUGIN"/></translation> +<translation id="5048533449481078685">ukazovateľ v zozname</translation> +<translation id="4202807286478387388">skok</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/> <ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Potvrďte, že naozaj chcete nainštalovať doplnkový modul <ph name="PLUGIN"/>. Mali by ste inštalovať len doplnkové moduly, ktorým dôverujete.</translation> +<translation id="7658239707568436148">Zrušiť</translation> +<translation id="795667975304826397">Nie je vybratý žiadny súbor</translation> +<translation id="1275511093094545429">Vyžaduje sa doplnkový modul <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Preberá sa doplnkový modul...</translation> +<translation id="8141602879876242471">Tento index sa dá prehľadávať. Zadajte kľúčové slová na vyhľadanie:</translation> +<translation id="6845533974506654842">stlačiť</translation> +<translation id="8244226242650769279">mapa obrázka</translation> +<translation id="1383141426028388991">Nepodarilo sa nainštalovať doplnok z adresy <ph name="URL"/></translation> +<translation id="2548326553472216322">Žiadne posledné vyhľadávania</translation> +<translation id="5944544982112848342">2048 (vysoký stupeň)</translation> +<translation id="3040011195152428237">odkaz</translation> +<translation id="8281246460978372009">Po inštalácii doplnkového modulu kliknite sem kvôli obnoveniu obsahu</translation> +<translation id="7364796246159120393">Vybrať súbor</translation> +<translation id="8964020114565522021">Súbor presunúť sem</translation> +<translation id="838869780401515933">označiť</translation> +<translation id="2846343701378493991">1024 (stredný stupeň)</translation> +<translation id="5476505524087279545">zrušiť označenie</translation> +<translation id="679352192834563463">Na zobrazenie tohto obsahu nie je k dispozícii žiadny doplnkový modul</translation> +<translation id="3789841737615482174">Inštalovať</translation> +<translation id="6663448176199120256">Posledné vyhľadávania</translation> +<translation id="3600343118165084788">Kliknite sem, ak chcete prevziať doplnkový modul</translation> +<translation id="6807599807928161586">webová oblasť</translation> +<translation id="5939518447894949180">Vynulovať</translation> +<translation id="3771786644471114952">Získať doplnkový modul</translation> +<translation id="1842960171412779397">vybrať</translation> +<translation id="6119846243427417423">aktivovať</translation> +<translation id="8444882422881193423">Počet súborov: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Potvrďte, že naozaj chcete nainštalovať tento doplnkový modul. Mali by ste inštalovať len doplnkové moduly, ktorým dôverujete.</translation> +<translation id="4838490908464673667">Nie je nainštalovaný vyžadovaný doplnkový modul.</translation> +<translation id="8597182159515967513">nadpis</translation> +<translation id="2653659639078652383">Odoslať</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_sl.xtb b/webkit/glue/resources/webkit_strings_sl.xtb new file mode 100644 index 0000000..4108745 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_sl.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sl"> +<translation id="4420062214988137980">Namestitev vtičnika ni bila uspešna</translation> +<translation id="1235745349614807883">Počisti zadnja iskanja</translation> +<translation id="3825324228893189080">Potreben je dodatni vtičnik</translation> +<translation id="2965480764085142436">Vtičnik <ph name="PLUGIN"/> ni nameščen</translation> +<translation id="5048533449481078685">označevalnik seznama</translation> +<translation id="4202807286478387388">skoči</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Prosimo potrdite, da želite namestiti vtičnik <ph name="PLUGIN"/>. Priporočamo vam, da namestite le vtičnike, ki so vredni zaupanja.</translation> +<translation id="7658239707568436148">Prekliči</translation> +<translation id="795667975304826397">Nobena datoteka ni izbrana</translation> +<translation id="1275511093094545429">Potreben je vtičnik <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Prenos vtičnika ...</translation> +<translation id="8141602879876242471">To je kazalo, ki omogoča iskanje. Vnesite ključne besede za iskanje:</translation> +<translation id="6845533974506654842">pritisni</translation> +<translation id="8244226242650769279">slikovni zemljevid</translation> +<translation id="1383141426028388991">Namestitev vtičnika z naslova <ph name="URL"/> ni bila uspešna</translation> +<translation id="2548326553472216322">Ni zadnjih iskanj</translation> +<translation id="5944544982112848342">2048 (visoka stopnja)</translation> +<translation id="3040011195152428237">povezava</translation> +<translation id="8281246460978372009">Po namestitvi vtičnika za osvežitev kliknite tu</translation> +<translation id="7364796246159120393">Izberi datoteko</translation> +<translation id="8964020114565522021">Datoteko povleci sem</translation> +<translation id="838869780401515933">potrdi</translation> +<translation id="2846343701378493991">1024 (srednja stopnja)</translation> +<translation id="5476505524087279545">počisti izbor</translation> +<translation id="679352192834563463">Za prikaz te vsebine ni na voljo noben vtičnik</translation> +<translation id="3789841737615482174">Namesti</translation> +<translation id="6663448176199120256">Zadnja iskanja</translation> +<translation id="3600343118165084788">Za prenos vtičnika kliknite tukaj</translation> +<translation id="6807599807928161586">spletno področje</translation> +<translation id="5939518447894949180">Ponastavi</translation> +<translation id="3771786644471114952">Dobite vtičnik</translation> +<translation id="1842960171412779397">izberi</translation> +<translation id="6119846243427417423">aktiviraj</translation> +<translation id="8444882422881193423">Število datotek: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Potrdite, da želite namestiti ta vtičnik. Priporočamo vam, da namestite le vtičnike, ki so vredni zaupanja.</translation> +<translation id="4838490908464673667">Potrebni vtičnik ni nameščen</translation> +<translation id="8597182159515967513">naslov</translation> +<translation id="2653659639078652383">Pošlji</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_sr.xtb b/webkit/glue/resources/webkit_strings_sr.xtb new file mode 100644 index 0000000..f4da790 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_sr.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sr"> +<translation id="4420062214988137980">Инсталирање додатне компоненте није успело</translation> +<translation id="1235745349614807883">Обриши недавне претраге</translation> +<translation id="3825324228893189080">Потребна је додатна компонента</translation> +<translation id="2965480764085142436">Додатна компонента <ph name="PLUGIN"/> није инсталирана</translation> +<translation id="5048533449481078685">означивач листе</translation> +<translation id="4202807286478387388">прескочи</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Потврдите да желите да инсталирате додатну компоненту <ph name="PLUGIN"/>. Требало би да инсталирате само додатне компоненте које сматрате поузданима.</translation> +<translation id="7658239707568436148">Откажи</translation> +<translation id="795667975304826397">Није одабрана ниједна датотека</translation> +<translation id="1275511093094545429">Потребна је додатна компонента <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Преузимање додатне компоненте...</translation> +<translation id="8141602879876242471">Ово је индекс који може да се претражује. Унесите кључне речи за претрагу:</translation> +<translation id="6845533974506654842">притисни</translation> +<translation id="8244226242650769279">мапа слике</translation> +<translation id="1383141426028388991">Неуспешно инсталирање додатне компоненте са <ph name="URL"/></translation> +<translation id="2548326553472216322">Нема недавних претрага</translation> +<translation id="5944544982112848342">2048 (високи степен)</translation> +<translation id="3040011195152428237">веза</translation> +<translation id="8281246460978372009">Након инсталирања додатне компоненте, кликните овде да бисте освежили</translation> +<translation id="7364796246159120393">Одаберите датотеку</translation> +<translation id="8964020114565522021">Превуците датотеку овде</translation> +<translation id="838869780401515933">изабери</translation> +<translation id="2846343701378493991">1024 (средњи степен)</translation> +<translation id="5476505524087279545">опозови избор</translation> +<translation id="679352192834563463">Нема доступне додатне компоненте за приказивање овог садржаја</translation> +<translation id="3789841737615482174">Инсталирај</translation> +<translation id="6663448176199120256">Недавне претраге</translation> +<translation id="3600343118165084788">Кликните овде да бисте преузели додатну компоненту</translation> +<translation id="6807599807928161586">веб област</translation> +<translation id="5939518447894949180">Ресетуј</translation> +<translation id="3771786644471114952">Набави додатну компоненту</translation> +<translation id="1842960171412779397">изабери</translation> +<translation id="6119846243427417423">активирај</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> датотеке(а)</translation> +<translation id="3926627843712816530">Потврдите да желите да инсталирате ову додатну компоненту. Требало би да инсталирате само додатне компоненте које сматрате поузданима.</translation> +<translation id="4838490908464673667">Потребна додатна компонента није инсталирана</translation> +<translation id="8597182159515967513">наслов</translation> +<translation id="2653659639078652383">Пошаљи</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_sv.xtb b/webkit/glue/resources/webkit_strings_sv.xtb new file mode 100644 index 0000000..4b7323c --- /dev/null +++ b/webkit/glue/resources/webkit_strings_sv.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sv"> +<translation id="4420062214988137980">Installationen av plugin-programmet misslyckades</translation> +<translation id="1235745349614807883">Rensa senaste sökningar</translation> +<translation id="3825324228893189080">Ytterligare plugin-program krävs</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/>-plugin-programmet har inte installerats</translation> +<translation id="5048533449481078685">listmarkör</translation> +<translation id="4202807286478387388">fortsätta</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Bekräfta att du vill installera <ph name="PLUGIN"/>-plugin-programmet. Installera bara tillförlitliga plugin-program.</translation> +<translation id="7658239707568436148">Avbryt</translation> +<translation id="795667975304826397">Ingen fil har valts</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/>-pluginprogram krävs</translation> +<translation id="8662565117025751661">Laddar ned plugin-program...</translation> +<translation id="8141602879876242471">Det här är ett sökbart index. Skriv sökord:</translation> +<translation id="6845533974506654842">tryck</translation> +<translation id="8244226242650769279">bildkarta</translation> +<translation id="1383141426028388991">Det gick inte att installera plugin-program från <ph name="URL"/></translation> +<translation id="2548326553472216322">Inga nya sökningar</translation> +<translation id="5944544982112848342">2048 (hög)</translation> +<translation id="3040011195152428237">länk</translation> +<translation id="8281246460978372009">Klicka här för att uppdatera när du har installerat plugin-programmet</translation> +<translation id="7364796246159120393">Välj fil</translation> +<translation id="8964020114565522021">Dra filen hit</translation> +<translation id="838869780401515933">kryssa för</translation> +<translation id="2846343701378493991">1024 (medel)</translation> +<translation id="5476505524087279545">kryssa av</translation> +<translation id="679352192834563463">Det finns inget plugin-program för att visa det här innehållet</translation> +<translation id="3789841737615482174">Installera</translation> +<translation id="6663448176199120256">Senaste sökningar</translation> +<translation id="3600343118165084788">Klicka här för att ladda ned plugin-program</translation> +<translation id="6807599807928161586">webbområde</translation> +<translation id="5939518447894949180">Återställ</translation> +<translation id="3771786644471114952">Hämta plugin-program</translation> +<translation id="1842960171412779397">välj</translation> +<translation id="6119846243427417423">aktivera</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> filer</translation> +<translation id="3926627843712816530">Bekräfta att du vill installera det här plugin-programmet. Installera bara tillförlitliga plugin-program.</translation> +<translation id="4838490908464673667">De begärda plugin-programmen har inte installerats</translation> +<translation id="8597182159515967513">rubrik</translation> +<translation id="2653659639078652383">Skicka</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_sw.xtb b/webkit/glue/resources/webkit_strings_sw.xtb new file mode 100644 index 0000000..9d798d8 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_sw.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sw"> +<translation id="4420062214988137980">Usanidi wa programu-jalizi haukufaulu</translation> +<translation id="1235745349614807883">Futa Utafutaji wa Hivi Karibuni</translation> +<translation id="3825324228893189080">Programu-jalizi inahitajika</translation> +<translation id="2965480764085142436">Programu-jalizi <ph name="PLUGIN"/> haijasanidiwa</translation> +<translation id="5048533449481078685">kialamishi orodha</translation> +<translation id="4202807286478387388">ruka</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Tafadali thibitisha kuwa ungependa kusanidi programu-jalizi <ph name="PLUGIN"/>. Unastahili kusanidi progrmau-jalizi unazoamini pekee.</translation> +<translation id="7658239707568436148">Ghairi</translation> +<translation id="795667975304826397">Hakuna faili iliyochaguliwa</translation> +<translation id="1275511093094545429">Programu-jalizi <ph name="PLUGIN"/> inahitajika</translation> +<translation id="8662565117025751661">Programu-jalizi inapakuliwa</translation> +<translation id="8141602879876242471">Hii ni fahirisi inayoweza kutafutwa. Weka maneno muhimu ya utafutaji.</translation> +<translation id="6845533974506654842">bofya</translation> +<translation id="8244226242650769279">ramani ya picha</translation> +<translation id="1383141426028388991">Imeshindwa kusanidi programu-jalizi kutoka <ph name="URL"/></translation> +<translation id="2548326553472216322">Hakuna utafutaji wa hivi karibuni</translation> +<translation id="5944544982112848342">2048 (Gredi ya Juu)</translation> +<translation id="3040011195152428237">kiungo</translation> +<translation id="8281246460978372009">Baada ya kusakinisha programu-jalizi, bonyeza hapa kuonyesha upya</translation> +<translation id="7364796246159120393">Chagua Faili</translation> +<translation id="8964020114565522021">Vuta faili hapa</translation> +<translation id="838869780401515933">chunguza</translation> +<translation id="2846343701378493991">1024 (Gredi Wastani)</translation> +<translation id="5476505524087279545">toa tiki</translation> +<translation id="679352192834563463">Hakuna programu-jalizi ya kuonyesha maudhui haya</translation> +<translation id="3789841737615482174">Sanidi</translation> +<translation id="6663448176199120256">Utafutaji wa hivi karibuni</translation> +<translation id="3600343118165084788">Bofya hapa kupakua programu-jalizi</translation> +<translation id="6807599807928161586">eneo wavuti</translation> +<translation id="5939518447894949180">Weka upya</translation> +<translation id="3771786644471114952">Pata programu-jalizi</translation> +<translation id="1842960171412779397">chagua</translation> +<translation id="6119846243427417423">wezesha</translation> +<translation id="8444882422881193423">faili <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Tafadali thibitisha kuwa ungependa kusanidi programu-jalizi hii. Unastahili kusanidi progrmau-jalizi unazoamini pekee.</translation> +<translation id="4838490908464673667">Programu-jalizi inayohitajika haijasanidiwa</translation> +<translation id="8597182159515967513">kichwa</translation> +<translation id="2653659639078652383">Wasilisha</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_ta.xtb b/webkit/glue/resources/webkit_strings_ta.xtb new file mode 100644 index 0000000..3b19b4c --- /dev/null +++ b/webkit/glue/resources/webkit_strings_ta.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ta"> +<translation id="4420062214988137980">செருகுநிரல் நிறுவல் தோல்வியடைந்தது</translation> +<translation id="1235745349614807883">சமீபத்திய தேடல்களை சுத்தமாக்கு</translation> +<translation id="3825324228893189080">கூடுதல் செருகுநிரல் தேவைப்படுகிறது</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> செருகுநிரல் நிறுவப்படவில்லை</translation> +<translation id="5048533449481078685">பட்டியல் குறிப்பான்</translation> +<translation id="4202807286478387388">தாவு</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143"><ph name="PLUGIN"/> செருகுநிரலை நிறுவ விரும்புகிறீர்கள் என்பதை உறுதிப்படுத்துக. நீங்கள் நம்பும் செருகுநிரல்களை மட்டுமே நிறுவ வேண்டும்.</translation> +<translation id="7658239707568436148">ரத்துசெய்</translation> +<translation id="795667975304826397">எந்த கோப்பும் தேர்ந்தெடுக்கப்டவில்லை</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> செருகுநிரல் தேவைப்படுகிறது</translation> +<translation id="8662565117025751661">செருகுநிரலைப் பதிவிறக்குகிறது...</translation> +<translation id="8141602879876242471">இது தேடக்கூடிய பொருளடக்கம். தேடல் சொற்களை உள்ளிடுக:</translation> +<translation id="6845533974506654842">அழுத்துக</translation> +<translation id="8244226242650769279">பட மேப்</translation> +<translation id="1383141426028388991"><ph name="URL"/> இலிருந்து செருகுநிரலை நிறுவுவதில் தோல்வியடைந்தது</translation> +<translation id="2548326553472216322">சமீபத்திய தேடல்கள் எதுவுமில்லை</translation> +<translation id="5944544982112848342">2048 (உயர் தரம்)</translation> +<translation id="3040011195152428237">இணைப்பு</translation> +<translation id="8281246460978372009">செருகுநிரலை நிறுவிய பின், புதுப்பிக்க இங்கு கிளிக் செய்க</translation> +<translation id="7364796246159120393">கோப்பைத் தேர்வு செய்க</translation> +<translation id="8964020114565522021">கோப்பை இங்கே இழுத்து வருக</translation> +<translation id="838869780401515933">சரிபார்</translation> +<translation id="2846343701378493991">1024 (இடைநிலைத் தரம்)</translation> +<translation id="5476505524087279545">தேர்வு நீக்கு</translation> +<translation id="679352192834563463">இந்த உள்ளடக்கத்தைக் காண்பிப்பதற்கான செருகுநிரல் கிடைக்கவில்லை</translation> +<translation id="3789841737615482174">நிறுவு</translation> +<translation id="6663448176199120256">சமீபத்திய தேடல்கள்</translation> +<translation id="3600343118165084788">செருகுநிரலைப் பதிவிறக்க இங்கே கிளிக் செய்க</translation> +<translation id="6807599807928161586">வலைப் பகுதி</translation> +<translation id="5939518447894949180">மீட்டமை</translation> +<translation id="3771786644471114952">செருகுநிரலைப் பெறுக</translation> +<translation id="1842960171412779397">தேர்ந்தெடு</translation> +<translation id="6119846243427417423">செயல்படுத்து</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> கோப்புகள்</translation> +<translation id="3926627843712816530">இந்த செருகுநிரலை நிறுவ விரும்புகிறீர்கள் என்பதை உறுதிப்படுத்துக. நீங்கள் நம்பும் செருகுநிரல்களை மட்டுமே நிறுவ வேண்டும்.</translation> +<translation id="4838490908464673667">தேவையான செருகுநிரல் நிறுவப்படவில்லை</translation> +<translation id="8597182159515967513">தலைப்பு</translation> +<translation id="2653659639078652383">சமர்ப்பி</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_te.xtb b/webkit/glue/resources/webkit_strings_te.xtb new file mode 100644 index 0000000..37e3265 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_te.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="te"> +<translation id="4420062214988137980">ప్లగ్ ఇన్ ఇన్స్టాలేషన్ విఫలమయ్యింది</translation> +<translation id="1235745349614807883">ఇటీవల శోధనలను క్లియర్ చెయ్యి</translation> +<translation id="3825324228893189080">అదనపు ప్లగ్ఇన్ అవసరం</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> ప్లగ్ఇన్ ఇన్స్టాల్ అవ్వలేదు</translation> +<translation id="5048533449481078685">జాబితా మార్కర్</translation> +<translation id="4202807286478387388">వెళ్ళు</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">దయచేసి <ph name="PLUGIN"/> ప్లగ్ఇన్ను ఇన్స్టాల్ చెయ్యాలనుకుంటునట్లు నిర్ధారించండి. మీరు నమ్మే ప్లగ్ఇన్లను మాత్రమే మీరు ఇన్స్టాల్ చెయ్యాలి.</translation> +<translation id="7658239707568436148">రద్దు చెయ్యి</translation> +<translation id="795667975304826397">ఫైల్ ఏదీ ఎంచుకోలేదు</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> ప్లగ్ఇన్ అవసరం</translation> +<translation id="8662565117025751661">ప్లగిన్ను డౌన్లోడ్ చేస్తోంది...</translation> +<translation id="8141602879876242471">ఇది ఒక శోధించగల సూచిక. శోధన కీవర్డ్లను ఎంటర్ చెయ్యండి:</translation> +<translation id="6845533974506654842">నొక్కండి</translation> +<translation id="8244226242650769279">చిత్రం మాప్</translation> +<translation id="1383141426028388991"><ph name="URL"/> నుండి ప్లగ్ఇన్ను ఇన్స్టాల్ చెయ్యడంలో విఫలమైంది</translation> +<translation id="2548326553472216322">ఇటీవల శోధనలు లేవు</translation> +<translation id="5944544982112848342">2048 (ఉత్తమ గ్రేడ్)</translation> +<translation id="3040011195152428237">లింక్</translation> +<translation id="8281246460978372009">ప్లగ్ఇన్ను ఇన్స్టాల్ చేసిన తర్వాత, రిఫ్రెష్ చెయ్యడానికి ఇక్కడ క్లిక్ చెయ్యండి.</translation> +<translation id="7364796246159120393">ఫైల్ను ఎంచుకోండి</translation> +<translation id="8964020114565522021">ఫైల్ను ఇక్కడకు లాగండి</translation> +<translation id="838869780401515933">తనిఖీ చెయ్యి</translation> +<translation id="2846343701378493991">1024 (మధ్యస్థ గ్రేడ్)</translation> +<translation id="5476505524087279545">ఎంపిక చెయ్యబడలేదు</translation> +<translation id="679352192834563463">ఈ కంటెంట్ను ప్రదర్శించడానికి ఏ ప్లగ్ఇన్ అందుబాటులో లేదు</translation> +<translation id="3789841737615482174">ఇన్స్టాల్ చెయ్యి</translation> +<translation id="6663448176199120256">ఇటీవల శోధనలు</translation> +<translation id="3600343118165084788">ప్లగ్ఇన్ను దిగుమతి చెయ్యడానికి ఇక్కడ క్లిక్ చెయ్యండి</translation> +<translation id="6807599807928161586">వెబ్ ప్రాంతం</translation> +<translation id="5939518447894949180">తిరిగి అమర్చండి</translation> +<translation id="3771786644471114952">ప్లగ్ఇన్ను పొందండి</translation> +<translation id="1842960171412779397">ఎంచుకోండి</translation> +<translation id="6119846243427417423">ఆక్టివేట్ చెయ్యి</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ఫైళ్ళు</translation> +<translation id="3926627843712816530">దయచేసి ఈ ప్లగ్ ఇన్ను ఇన్స్టాల్ చెయ్యాలనుకుంటునట్లు నిర్ధారించండి. మీరు నమ్మే ప్లగ్ఇన్లను మాత్రమే మీరు ఇన్స్టాల్ చెయ్యాలి.</translation> +<translation id="4838490908464673667">అవసరమైన ప్లగిన్ ఇన్స్టాల్ చెయ్యబడలేదు</translation> +<translation id="8597182159515967513">శీర్షిక</translation> +<translation id="2653659639078652383">సమర్పించు</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_th.xtb b/webkit/glue/resources/webkit_strings_th.xtb new file mode 100644 index 0000000..a6c8b4b --- /dev/null +++ b/webkit/glue/resources/webkit_strings_th.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="th"> +<translation id="4420062214988137980">การติดตั้งปลั๊กอินล้มเหลว</translation> +<translation id="1235745349614807883">ลบการค้นหาล่าสุด</translation> +<translation id="3825324228893189080">ต้องการปลั๊กอินเพิ่มเติม</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> ไม่ได้ติดตั้งปลั๊กอิน</translation> +<translation id="5048533449481078685">ผู้สร้างรายการ</translation> +<translation id="4202807286478387388">ข้าม</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">โปรดยืนยันว่าคุณต้องการที่จะติดตั้ง <ph name="PLUGIN"/> ปลั๊กอิน คุณควรติดตั้งปลั๊กอินที่คุณเชื่อถือเท่านั้น</translation> +<translation id="7658239707568436148">ยกเลิก</translation> +<translation id="795667975304826397">ไม่ได้เลือกไฟล์ใด</translation> +<translation id="1275511093094545429">ต้องใช้ปลั๊กอิน <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">กำลังดาวน์โหลดปลั๊กอิน...</translation> +<translation id="8141602879876242471">นี่คือดัชนีที่สามารถค้นหาได้ ป้อนคำหลักในการค้นหา:</translation> +<translation id="6845533974506654842">กด</translation> +<translation id="8244226242650769279">แผนที่รูปภาพ</translation> +<translation id="1383141426028388991">การติดตั้งปลั๊กอินจาก <ph name="URL"/> ล้มเหลว</translation> +<translation id="2548326553472216322">ไม่พบการค้นหาล่าสุด</translation> +<translation id="5944544982112848342">2048 (เกรดสูง)</translation> +<translation id="3040011195152428237">ลิงก์</translation> +<translation id="8281246460978372009">หลังจากติดตั้งปลั๊กอิน คลิกที่นี่เพื่อรีเฟรซ</translation> +<translation id="7364796246159120393">เลือกไฟล์</translation> +<translation id="8964020114565522021">ลากไฟล์มาที่นี่</translation> +<translation id="838869780401515933">ทำเครื่องหมาย</translation> +<translation id="2846343701378493991">1024 (เกรดปานกลาง)</translation> +<translation id="5476505524087279545">ยกเลิกการทำเครื่องหมาย</translation> +<translation id="679352192834563463">ไม่มีปลั๊กอินที่จะแสดงเนื้อหานี้</translation> +<translation id="3789841737615482174">ติดตั้ง</translation> +<translation id="6663448176199120256">การค้นหาล่าสุด</translation> +<translation id="3600343118165084788">คลิกที่นี่เพื่อดาวน์โหลดปลั๊กอิน</translation> +<translation id="6807599807928161586">พื้นที่เว็บ</translation> +<translation id="5939518447894949180">ตั้งค่าใหม่</translation> +<translation id="3771786644471114952">ติดตั้งปลั๊กอิน</translation> +<translation id="1842960171412779397">เลือก</translation> +<translation id="6119846243427417423">เปิดใช้งาน</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> ไฟล์</translation> +<translation id="3926627843712816530">โปรดยืนยันว่าคุณต้องการติดตั้งปลั๊กอินนี้ คุณควรติดตั้งปลั๊กอินที่คุณเชื่อถือเท่านั้น</translation> +<translation id="4838490908464673667">ไม่ได้ติดตั้งปลั๊กอินที่จำเป็น</translation> +<translation id="8597182159515967513">ส่วนหัว</translation> +<translation id="2653659639078652383">ส่ง</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_tr.xtb b/webkit/glue/resources/webkit_strings_tr.xtb new file mode 100644 index 0000000..1f5571a --- /dev/null +++ b/webkit/glue/resources/webkit_strings_tr.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="tr"> +<translation id="4420062214988137980">Eklenti kurulumu başarısız oldu</translation> +<translation id="1235745349614807883">Son Aramaları Temizle</translation> +<translation id="3825324228893189080">Ek eklenti gerekiyor</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> eklentisi yüklü değil</translation> +<translation id="5048533449481078685">liste işaretçisi</translation> +<translation id="4202807286478387388">git</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Lütfen <ph name="PLUGIN"/> eklentisini yüklemek istediğinizi onaylayın. Yalnızca güvendiğiniz eklentileri yüklemelisiniz.</translation> +<translation id="7658239707568436148">İptal</translation> +<translation id="795667975304826397">Dosya seçilmedi</translation> +<translation id="1275511093094545429"><ph name="PLUGIN"/> eklentisi gerekiyor</translation> +<translation id="8662565117025751661">Eklenti indiriliyor...</translation> +<translation id="8141602879876242471">Bu dizinde arama yapılabilir. Arama anahtar kelimeleri girin:</translation> +<translation id="6845533974506654842">bas</translation> +<translation id="8244226242650769279">resim haritası</translation> +<translation id="1383141426028388991">Eklenti, <ph name="URL"/> kaynağından yüklenemedi</translation> +<translation id="2548326553472216322">Yeni arama yok</translation> +<translation id="5944544982112848342">2048 (Yüksek Düzey)</translation> +<translation id="3040011195152428237">bağlantı</translation> +<translation id="8281246460978372009">Eklentiyi yükledikten sonra yenilemek için burayı tıklayın</translation> +<translation id="7364796246159120393">Dosya Seç</translation> +<translation id="8964020114565522021">Dosyayı buraya sürükleyin</translation> +<translation id="838869780401515933">işaretle</translation> +<translation id="2846343701378493991">1024 (Orta Düzey)</translation> +<translation id="5476505524087279545">işareti kaldır</translation> +<translation id="679352192834563463">Bu içeriği görüntüleyecek eklenti yok</translation> +<translation id="3789841737615482174">Yükle</translation> +<translation id="6663448176199120256">Son Aramalar</translation> +<translation id="3600343118165084788">Eklentiyi indirmek için burayı tıklayın</translation> +<translation id="6807599807928161586">web alanı</translation> +<translation id="5939518447894949180">Sıfırla</translation> +<translation id="3771786644471114952">Eklenti Al</translation> +<translation id="1842960171412779397">seç</translation> +<translation id="6119846243427417423">etkinleştir</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> dosya</translation> +<translation id="3926627843712816530">Lütfen bu eklentiyi yüklemek istediğinizi onaylayın. Yalnızca güvendiğiniz eklentileri yüklemelisiniz.</translation> +<translation id="4838490908464673667">Gereken eklenti yüklü değil</translation> +<translation id="8597182159515967513">başlık</translation> +<translation id="2653659639078652383">Gönder</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_uk.xtb b/webkit/glue/resources/webkit_strings_uk.xtb new file mode 100644 index 0000000..ae193fe --- /dev/null +++ b/webkit/glue/resources/webkit_strings_uk.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="uk"> +<translation id="4420062214988137980">Не вдалось інсталювати модуль</translation> +<translation id="1235745349614807883">Очистити останні пошуки</translation> +<translation id="3825324228893189080">Потрібен додатковий модуль.</translation> +<translation id="2965480764085142436">Модуль <ph name="PLUGIN"/> не інстальовано</translation> +<translation id="5048533449481078685">маркер списку</translation> +<translation id="4202807286478387388">перейти</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Підтвердьте інсталяцію модуля <ph name="PLUGIN"/>. Слід інсталювати лише модулі, які вважаєте надійними.</translation> +<translation id="7658239707568436148">Скасувати</translation> +<translation id="795667975304826397">Файл не вибрано</translation> +<translation id="1275511093094545429">Потрібен модуль <ph name="PLUGIN"/></translation> +<translation id="8662565117025751661">Завантаження модуля...</translation> +<translation id="8141602879876242471">Цей доступний для пошуку індекс. Введіть ключові слова пошуку:</translation> +<translation id="6845533974506654842">натиснути</translation> +<translation id="8244226242650769279">мапа зображення</translation> +<translation id="1383141426028388991">Не вдалося встановити модуль із <ph name="URL"/></translation> +<translation id="2548326553472216322">Немає останніх пошуків</translation> +<translation id="5944544982112848342">2048 (Високий рівень)</translation> +<translation id="3040011195152428237">посилання</translation> +<translation id="8281246460978372009">Після інсталяції модуля натисніть тут, щоб оновити</translation> +<translation id="7364796246159120393">Вибрати файл</translation> +<translation id="8964020114565522021">Перетягніть файл сюди</translation> +<translation id="838869780401515933">установити прапорець</translation> +<translation id="2846343701378493991">1024 (Середній рівень)</translation> +<translation id="5476505524087279545">зняти прапорець</translation> +<translation id="679352192834563463">Немає доступного модуля для відображення цього вмісту</translation> +<translation id="3789841737615482174">Інсталювати</translation> +<translation id="6663448176199120256">Останні пошуки</translation> +<translation id="3600343118165084788">Натисніть тут, щоб завантажити модуль</translation> +<translation id="6807599807928161586">область Інтернету</translation> +<translation id="5939518447894949180">Скинути</translation> +<translation id="3771786644471114952">Отримати модуль</translation> +<translation id="1842960171412779397">вибрати</translation> +<translation id="6119846243427417423">активувати</translation> +<translation id="8444882422881193423">файлів: <ph name="NUMBER_OF_FILES"/></translation> +<translation id="3926627843712816530">Підтвердьте інсталяцію цього модуля. Слід інсталювати лише модулі, які вважаєте надійними.</translation> +<translation id="4838490908464673667">Потрібний модуль не інстальовано</translation> +<translation id="8597182159515967513">заголовок</translation> +<translation id="2653659639078652383">Надіслати</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_vi.xtb b/webkit/glue/resources/webkit_strings_vi.xtb new file mode 100644 index 0000000..9e3f498 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_vi.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="vi"> +<translation id="4420062214988137980">Không cài đặt plugin được</translation> +<translation id="1235745349614807883">Xoá Tìm kiếm Gần đây</translation> +<translation id="3825324228893189080">Cần plugin bổ sung</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> plgin không được cài đặt</translation> +<translation id="5048533449481078685">đánh dấu danh sách</translation> +<translation id="4202807286478387388">chuyển</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">Vui lòng xác nhận rằng bạn muốn cài đặt plugin <ph name="PLUGIN"/>. Bạn chỉ nên cài đặt plugin mà mình tin cậy.</translation> +<translation id="7658239707568436148">Huỷ</translation> +<translation id="795667975304826397">Không có tệp nào được chọn</translation> +<translation id="1275511093094545429">Cần <ph name="PLUGIN"/> plugin</translation> +<translation id="8662565117025751661">Đang tải xuống plugin...</translation> +<translation id="8141602879876242471">Đây là chỉ mục có thể tìm kiếm. Nhập từ khoá tìm kiếm vào:</translation> +<translation id="6845533974506654842">nhấn</translation> +<translation id="8244226242650769279">bản đồ hình ảnh</translation> +<translation id="1383141426028388991">Không cài đặt được plugin từ <ph name="URL"/></translation> +<translation id="2548326553472216322">Không có tìm kiếm nào gần đây</translation> +<translation id="5944544982112848342">2048 (Cấp độ cao)</translation> +<translation id="3040011195152428237">liên kết</translation> +<translation id="8281246460978372009">Sau khi cài đặt plugin, nhấp vào đây để làm mới</translation> +<translation id="7364796246159120393">Chọn Tệp tin</translation> +<translation id="8964020114565522021">Kéo tệp tại đây</translation> +<translation id="838869780401515933">chọn</translation> +<translation id="2846343701378493991">1024 (Loại Trung bình)</translation> +<translation id="5476505524087279545">bỏ chọn</translation> +<translation id="679352192834563463">Không plugin nào có sẵn để hiển thị nội dung này</translation> +<translation id="3789841737615482174">Cài đặt</translation> +<translation id="6663448176199120256">Tìm kiếm Gần đây</translation> +<translation id="3600343118165084788">Nhấp vào đây để tải plugin xuống</translation> +<translation id="6807599807928161586">khu vực web</translation> +<translation id="5939518447894949180">Đặt lại</translation> +<translation id="3771786644471114952">Tải Plugin</translation> +<translation id="1842960171412779397">chọn</translation> +<translation id="6119846243427417423">kích hoạt</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> tệp</translation> +<translation id="3926627843712816530">Hãy xác nhận rằng bạn muốn cài đặt plugin này. Bạn chỉ nên cài đặt plugin mà mình tin cậy.</translation> +<translation id="4838490908464673667">Plugin yêu cầu không được cài đặt</translation> +<translation id="8597182159515967513">đầu đề</translation> +<translation id="2653659639078652383">Gửi</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_zh-CN.xtb b/webkit/glue/resources/webkit_strings_zh-CN.xtb new file mode 100644 index 0000000..c2d8176 --- /dev/null +++ b/webkit/glue/resources/webkit_strings_zh-CN.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-CN"> +<translation id="4420062214988137980">安装插件失败</translation> +<translation id="1235745349614807883">清除最近的搜索</translation> +<translation id="3825324228893189080">需要使用其他插件</translation> +<translation id="2965480764085142436">未安装 <ph name="PLUGIN"/> 插件</translation> +<translation id="5048533449481078685">列表标记</translation> +<translation id="4202807286478387388">略过</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">请确认您要安装 <ph name="PLUGIN"/> 插件。您只能安装自己信任的插件。</translation> +<translation id="7658239707568436148">取消</translation> +<translation id="795667975304826397">未选择文件</translation> +<translation id="1275511093094545429">需要使用 <ph name="PLUGIN"/> 插件</translation> +<translation id="8662565117025751661">正在下载插件...</translation> +<translation id="8141602879876242471">这是一个可搜索的索引。请输入搜索关键字:</translation> +<translation id="6845533974506654842">按</translation> +<translation id="8244226242650769279">图片映射</translation> +<translation id="1383141426028388991">无法从 <ph name="URL"/> 安装插件</translation> +<translation id="2548326553472216322">最近未执行搜索</translation> +<translation id="5944544982112848342">2048(高强度)</translation> +<translation id="3040011195152428237">链接</translation> +<translation id="8281246460978372009">安装插件后,点击此处可刷新</translation> +<translation id="7364796246159120393">选择文件</translation> +<translation id="8964020114565522021">将文件拖到此处</translation> +<translation id="838869780401515933">选中</translation> +<translation id="2846343701378493991">1024(中等强度)</translation> +<translation id="5476505524087279545">取消选中</translation> +<translation id="679352192834563463">没有用于显示这种内容的插件</translation> +<translation id="3789841737615482174">安装</translation> +<translation id="6663448176199120256">近期搜索</translation> +<translation id="3600343118165084788">点击此处可下载插件</translation> +<translation id="6807599807928161586">网络区域</translation> +<translation id="5939518447894949180">重置</translation> +<translation id="3771786644471114952">获取插件</translation> +<translation id="1842960171412779397">选中</translation> +<translation id="6119846243427417423">激活</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> 个文件</translation> +<translation id="3926627843712816530">请确认您要安装此插件。您只能安装自己信任的插件。</translation> +<translation id="4838490908464673667">未安装所需插件</translation> +<translation id="8597182159515967513">标题</translation> +<translation id="2653659639078652383">提交</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/webkit_strings_zh-TW.xtb b/webkit/glue/resources/webkit_strings_zh-TW.xtb new file mode 100644 index 0000000..999207a --- /dev/null +++ b/webkit/glue/resources/webkit_strings_zh-TW.xtb @@ -0,0 +1,43 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-TW"> +<translation id="4420062214988137980">外掛程式安裝失敗</translation> +<translation id="1235745349614807883">清除最近的搜尋記錄</translation> +<translation id="3825324228893189080">需要額外的外掛程式</translation> +<translation id="2965480764085142436"><ph name="PLUGIN"/> 外掛程式尚未安裝</translation> +<translation id="5048533449481078685">清單標記</translation> +<translation id="4202807286478387388">跳至另一頁</translation> +<translation id="4611115858363067980"><ph name="FILENAME"/><ph name="WIDTH"/>×<ph name="HEIGHT"/></translation> +<translation id="4317653869502688143">請確定您想要安裝 <ph name="PLUGIN"/> 外掛程式。建議您僅安裝可靠的外掛程式。</translation> +<translation id="7658239707568436148">取消</translation> +<translation id="795667975304826397">未選擇檔案</translation> +<translation id="1275511093094545429">需要 <ph name="PLUGIN"/> 外掛程式</translation> +<translation id="8662565117025751661">正在下載外掛程式...</translation> +<translation id="8141602879876242471">這是可搜尋的索引,輸入搜尋關鍵字:</translation> +<translation id="6845533974506654842">按下</translation> +<translation id="8244226242650769279">影像地圖</translation> +<translation id="1383141426028388991">無法從 <ph name="URL"/> 安裝外掛程式</translation> +<translation id="2548326553472216322">沒有近期的搜尋</translation> +<translation id="5944544982112848342">2048 (高級)</translation> +<translation id="3040011195152428237">連結</translation> +<translation id="8281246460978372009">安裝外掛程式後,請按一下這裡更新</translation> +<translation id="7364796246159120393">選擇檔案</translation> +<translation id="8964020114565522021">拖曳檔案至此</translation> +<translation id="838869780401515933">選取</translation> +<translation id="2846343701378493991">1024 (中等)</translation> +<translation id="5476505524087279545">取消選取</translation> +<translation id="679352192834563463">沒有外掛程式可供顯示目前內容</translation> +<translation id="3789841737615482174">安裝</translation> +<translation id="6663448176199120256">最近的搜尋</translation> +<translation id="3600343118165084788">按一下這裡以下載掛程式</translation> +<translation id="6807599807928161586">網頁範圍</translation> +<translation id="5939518447894949180">重設</translation> +<translation id="3771786644471114952">取得外掛程式</translation> +<translation id="1842960171412779397">選取</translation> +<translation id="6119846243427417423">啟動</translation> +<translation id="8444882422881193423"><ph name="NUMBER_OF_FILES"/> 個檔案</translation> +<translation id="3926627843712816530">請確定您想要安裝此外掛程式。建議您僅安裝可靠的外掛程式。</translation> +<translation id="4838490908464673667">未安裝要求的外掛程式</translation> +<translation id="8597182159515967513">標題</translation> +<translation id="2653659639078652383">提交</translation> +</translationbundle>
\ No newline at end of file diff --git a/webkit/glue/resources/zoom_in.cur b/webkit/glue/resources/zoom_in.cur Binary files differnew file mode 100644 index 0000000..b594d79 --- /dev/null +++ b/webkit/glue/resources/zoom_in.cur diff --git a/webkit/glue/resources/zoom_out.cur b/webkit/glue/resources/zoom_out.cur Binary files differnew file mode 100644 index 0000000..7e495fb --- /dev/null +++ b/webkit/glue/resources/zoom_out.cur diff --git a/webkit/glue/scoped_clipboard_writer_glue.h b/webkit/glue/scoped_clipboard_writer_glue.h new file mode 100644 index 0000000..7460ddf --- /dev/null +++ b/webkit/glue/scoped_clipboard_writer_glue.h @@ -0,0 +1,32 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SCOPED_CLIPBOARD_WRITER_GLUE_H_ +#define SCOPED_CLIPBOARD_WRITER_GLUE_H_ + +#include "app/clipboard/scoped_clipboard_writer.h" + +class SkBitmap; + +namespace base { +class SharedMemory; +} + +class ScopedClipboardWriterGlue : public ScopedClipboardWriter { + public: + ScopedClipboardWriterGlue(Clipboard* clipboard) + : ScopedClipboardWriter(clipboard), + shared_buf_(NULL) { + } + + ~ScopedClipboardWriterGlue(); + + void WriteBitmapFromPixels(const void* pixels, const gfx::Size& size); + + private: + base::SharedMemory* shared_buf_; + DISALLOW_COPY_AND_ASSIGN(ScopedClipboardWriterGlue); +}; + +#endif // SCOPED_CLIPBOARD_WRITER_GLUE_H_ diff --git a/webkit/glue/simple_webmimeregistry_impl.cc b/webkit/glue/simple_webmimeregistry_impl.cc new file mode 100644 index 0000000..5dd227f --- /dev/null +++ b/webkit/glue/simple_webmimeregistry_impl.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2010 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 "webkit/glue/simple_webmimeregistry_impl.h" + +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "net/base/mime_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebString; +using WebKit::WebMimeRegistry; + +namespace { + +// Convert a WebString to ASCII, falling back on an empty string in the case +// of a non-ASCII string. +std::string ToASCIIOrEmpty(const WebString& string) { + if (!IsStringASCII(string)) + return std::string(); + return UTF16ToASCII(string); +} + +} // namespace + +namespace webkit_glue { + +WebMimeRegistry::SupportsType SimpleWebMimeRegistryImpl::supportsMIMEType( + const WebString& mime_type) { + if (!net::IsSupportedMimeType(ToASCIIOrEmpty(mime_type).c_str())) + return WebMimeRegistry::IsNotSupported; + return WebMimeRegistry::IsSupported; +} + +WebMimeRegistry::SupportsType SimpleWebMimeRegistryImpl::supportsImageMIMEType( + const WebString& mime_type) { + if (!net::IsSupportedImageMimeType(ToASCIIOrEmpty(mime_type).c_str())) + return WebMimeRegistry::IsNotSupported; + return WebMimeRegistry::IsSupported; +} + +WebMimeRegistry::SupportsType SimpleWebMimeRegistryImpl::supportsJavaScriptMIMEType( + const WebString& mime_type) { + if (!net::IsSupportedJavascriptMimeType(ToASCIIOrEmpty(mime_type).c_str())) + return WebMimeRegistry::IsNotSupported; + return WebMimeRegistry::IsSupported; +} + +WebMimeRegistry::SupportsType SimpleWebMimeRegistryImpl::supportsMediaMIMEType( + const WebString& mime_type, const WebString& codecs) { + // Not supporting the container is a flat-out no. + if (!net::IsSupportedMediaMimeType(ToASCIIOrEmpty(mime_type).c_str())) + return IsNotSupported; + + // Check list of strict codecs to see if it is supported. + if (net::IsStrictMediaMimeType(ToASCIIOrEmpty(mime_type).c_str())) { + // We support the container, but no codecs were specified. + if (codecs.isNull()) + return MayBeSupported; + + // Check if the codecs are a perfect match. + std::vector<std::string> strict_codecs; + net::ParseCodecString(ToASCIIOrEmpty(codecs).c_str(), + &strict_codecs, + false); + if (!net::IsSupportedStrictMediaMimeType(ToASCIIOrEmpty(mime_type).c_str(), + strict_codecs)) + return IsNotSupported; + + // Good to go! + return IsSupported; + } + + // If we don't recognize the codec, it's possible we support it. + std::vector<std::string> parsed_codecs; + net::ParseCodecString(ToASCIIOrEmpty(codecs).c_str(), &parsed_codecs, true); + if (!net::AreSupportedMediaCodecs(parsed_codecs)) + return MayBeSupported; + + // Otherwise we have a perfect match. + return IsSupported; +} + +WebMimeRegistry::SupportsType SimpleWebMimeRegistryImpl::supportsNonImageMIMEType( + const WebString& mime_type) { + if (!net::IsSupportedNonImageMimeType(ToASCIIOrEmpty(mime_type).c_str())) + return WebMimeRegistry::IsNotSupported; + return WebMimeRegistry::IsSupported; +} + +WebString SimpleWebMimeRegistryImpl::mimeTypeForExtension( + const WebString& file_extension) { + std::string mime_type; + net::GetMimeTypeFromExtension( + WebStringToFilePathString(file_extension), &mime_type); + return ASCIIToUTF16(mime_type); +} + +WebString SimpleWebMimeRegistryImpl::mimeTypeFromFile( + const WebString& file_path) { + std::string mime_type; + net::GetMimeTypeFromFile( + FilePath(WebStringToFilePathString(file_path)), &mime_type); + return ASCIIToUTF16(mime_type); +} + +WebString SimpleWebMimeRegistryImpl::preferredExtensionForMIMEType( + const WebString& mime_type) { + FilePath::StringType file_extension; + net::GetPreferredExtensionForMimeType(ToASCIIOrEmpty(mime_type), + &file_extension); + return FilePathStringToWebString(file_extension); +} + +} // namespace webkit_glue diff --git a/webkit/glue/simple_webmimeregistry_impl.h b/webkit/glue/simple_webmimeregistry_impl.h new file mode 100644 index 0000000..0056de0 --- /dev/null +++ b/webkit/glue/simple_webmimeregistry_impl.h @@ -0,0 +1,33 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#ifndef WEBMIMEREGISTRY_IMPL_H_ +#define WEBMIMEREGISTRY_IMPL_H_ + +#include "third_party/WebKit/WebKit/chromium/public/WebMimeRegistry.h" + +namespace webkit_glue { + +class SimpleWebMimeRegistryImpl : public WebKit::WebMimeRegistry { + public: + // WebMimeRegistry methods: + virtual WebKit::WebMimeRegistry::SupportsType supportsMIMEType( + const WebKit::WebString&); + virtual WebKit::WebMimeRegistry::SupportsType supportsImageMIMEType( + const WebKit::WebString&); + virtual WebKit::WebMimeRegistry::SupportsType supportsJavaScriptMIMEType( + const WebKit::WebString&); + virtual WebKit::WebMimeRegistry::SupportsType supportsMediaMIMEType( + const WebKit::WebString&, const WebKit::WebString&); + virtual WebKit::WebMimeRegistry::SupportsType supportsNonImageMIMEType( + const WebKit::WebString&); + virtual WebKit::WebString mimeTypeForExtension(const WebKit::WebString&); + virtual WebKit::WebString mimeTypeFromFile(const WebKit::WebString&); + virtual WebKit::WebString preferredExtensionForMIMEType( + const WebKit::WebString&); +}; + +} // namespace webkit_glue + +#endif // WEBMIMEREGISTRY_IMPL_H_ diff --git a/webkit/glue/site_isolation_metrics.cc b/webkit/glue/site_isolation_metrics.cc new file mode 100644 index 0000000..912263f --- /dev/null +++ b/webkit/glue/site_isolation_metrics.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2010 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 "webkit/glue/site_isolation_metrics.h" + +#include <set> + +#include "base/hash_tables.h" +#include "base/histogram.h" +#include "net/base/mime_sniffer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSecurityOrigin.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" + +using WebKit::WebFrame; +using WebKit::WebSecurityOrigin; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +typedef base::hash_map<unsigned, WebURLRequest::TargetType> TargetTypeMap; +typedef base::hash_map<std::string, int> MimeTypeMap; +typedef std::set<std::string> CrossOriginTextHtmlResponseSet; + +static TargetTypeMap* GetTargetTypeMap() { + static TargetTypeMap target_type_map_; + return &target_type_map_; +} + +// Copied from net/base/mime_util.cc, supported_non_image_types[] +static const char* const kCrossOriginMimeTypesToLog[] = { + "text/cache-manifest", + "text/html", + "text/xml", + "text/xsl", + "text/plain", + "text/vnd.chromium.ftp-dir", + "text/", + "text/css", + "image/svg+xml", + "application/xml", + "application/xhtml+xml", + "application/rss+xml", + "application/atom+xml", + "application/json", + "application/x-x509-user-cert", + "multipart/x-mixed-replace", + "(NONE)" // Keep track of missing MIME types as well +}; + +static MimeTypeMap* GetMimeTypeMap() { + static MimeTypeMap mime_type_map_; + if (!mime_type_map_.size()) { + for (size_t i = 0; i < arraysize(kCrossOriginMimeTypesToLog); ++i) + mime_type_map_[kCrossOriginMimeTypesToLog[i]] = i; + } + return &mime_type_map_; +} + +// This is set is used to keep track of the response urls that we want to +// sniff, since we will have to wait for the payload to arrive. +static CrossOriginTextHtmlResponseSet* GetCrossOriginTextHtmlResponseSet() { + static CrossOriginTextHtmlResponseSet cross_origin_text_html_response_set_; + return &cross_origin_text_html_response_set_; +} + +static void LogVerifiedTextHtmlResponse() { + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_verified_texthtml_BLOCK", 1); +} + +static void LogMislabeledTextHtmlResponse() { + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_mislabeled_texthtml", 1); +} + +void SiteIsolationMetrics::AddRequest(unsigned identifier, + WebURLRequest::TargetType target_type) { + TargetTypeMap& target_type_map = *GetTargetTypeMap(); + target_type_map[identifier] = target_type; +} + +// Check whether the given response is allowed due to access control headers. +// This is basically a copy of the logic of passesAccessControlCheck() in +// WebCore/loader/CrossOriginAccessControl.cpp. +bool SiteIsolationMetrics::AllowedByAccessControlHeader( + WebFrame* frame, const WebURLResponse& response) { + WebString access_control_origin = response.httpHeaderField( + WebString::fromUTF8("Access-Control-Allow-Origin")); + WebSecurityOrigin security_origin = + WebSecurityOrigin::createFromString(access_control_origin); + return access_control_origin == WebString::fromUTF8("*") || + frame->securityOrigin().canAccess(security_origin); +} + +// We want to log any cross-site request that we don't think a renderer should +// be allowed to make. We can safely ignore frame requests (since we'd like +// those to be in a separate renderer) and plugin requests, even if they are +// cross-origin. +// +// For comparison, we keep counts of: +// - All requests made by a renderer +// - All cross-site requests +// +// Then, for cross-site non-frame/plugin requests, we keep track of: +// - Counts for MIME types of interest +// - Counts of those MIME types that carry CORS headers +// - Counts of mislabeled text/html responses (without CORS) +// As well as those we would block: +// - Counts of verified text/html responses (without CORS) +// - Counts of XML/JSON responses (without CORS) +// +// This will let us say what percentage of requests we would end up blocking. +void SiteIsolationMetrics::LogMimeTypeForCrossOriginRequest( + WebFrame* frame, unsigned identifier, const WebURLResponse& response) { + UMA_HISTOGRAM_COUNTS("SiteIsolation.Requests", 1); + + TargetTypeMap& target_type_map = *GetTargetTypeMap(); + TargetTypeMap::iterator iter = target_type_map.find(identifier); + if (iter != target_type_map.end()) { + WebURLRequest::TargetType target_type = iter->second; + target_type_map.erase(iter); + + // Focus on cross-site requests. + if (!frame->securityOrigin().canAccess( + WebSecurityOrigin::create(response.url()))) { + UMA_HISTOGRAM_COUNTS("SiteIsolation.CrossSiteRequests", 1); + + // Now focus on non-frame, non-plugin requests. + if (target_type != WebURLRequest::TargetIsMainFrame && + target_type != WebURLRequest::TargetIsSubFrame && + target_type != WebURLRequest::TargetIsObject) { + // If it is part of a MIME type we might block, log the MIME type. + std::string mime_type = response.mimeType().utf8(); + MimeTypeMap mime_type_map = *GetMimeTypeMap(); + // Also track it if it lacks a MIME type. + // TODO(creis): 304 responses have no MIME type, so we don't handle + // them correctly. Can we look up their MIME type from the cache? + if (mime_type == "") + mime_type = "(NONE)"; + MimeTypeMap::iterator mime_type_iter = mime_type_map.find(mime_type); + if (mime_type_iter != mime_type_map.end()) { + UMA_HISTOGRAM_ENUMERATION( + "SiteIsolation.CrossSiteNonFrameResponse_MIME_Type", + mime_type_iter->second, + arraysize(kCrossOriginMimeTypesToLog)); + + // We also check access control headers, in case this + // cross-origin request has been explicitly permitted. + if (AllowedByAccessControlHeader(frame, response)) { + UMA_HISTOGRAM_ENUMERATION( + "SiteIsolation.CrossSiteNonFrameResponse_With_CORS_MIME_Type", + mime_type_iter->second, + arraysize(kCrossOriginMimeTypesToLog)); + } else { + // Without access control headers, we might block this request. + // Sometimes resources are mislabled as text/html, though, and we + // should only block them if we can verify that. To do so, we sniff + // the content once we have some of the payload. + if (mime_type == "text/html") { + // Remember the response until we can sniff its contents. + GetCrossOriginTextHtmlResponseSet()->insert( + response.url().spec()); + } else if (mime_type == "text/xml" || + mime_type == "text/xsl" || + mime_type == "application/xml" || + mime_type == "application/xhtml+xml" || + mime_type == "application/rss+xml" || + mime_type == "application/atom+xml" || + mime_type == "application/json") { + // We will also block XML and JSON MIME types for cross-site + // non-frame requests without CORS headers. + UMA_HISTOGRAM_COUNTS( + "SiteIsolation.CrossSiteNonFrameResponse_xml_or_json_BLOCK", + 1); + } + } + } + } + } + } +} + +void SiteIsolationMetrics::SniffCrossOriginHTML(const WebURL& response_url, + const char* data, + int len) { + if (!response_url.isValid()) + return; + + // Look up the URL to see if it is a text/html request we are tracking. + CrossOriginTextHtmlResponseSet& cross_origin_text_html_response_set = + *GetCrossOriginTextHtmlResponseSet(); + CrossOriginTextHtmlResponseSet::iterator request_iter = + cross_origin_text_html_response_set.find(response_url.spec()); + if (request_iter != cross_origin_text_html_response_set.end()) { + // Log whether it actually looks like HTML. + std::string sniffed_mime_type; + bool successful = net::SniffMimeType(data, len, response_url, + "", &sniffed_mime_type); + if (successful && sniffed_mime_type == "text/html") + LogVerifiedTextHtmlResponse(); + else + LogMislabeledTextHtmlResponse(); + cross_origin_text_html_response_set.erase(request_iter); + } +} + +void SiteIsolationMetrics::RemoveCompletedResponse( + const WebURL& response_url) { + if (!response_url.isValid()) + return; + + // Ensure we don't leave responses in the set after they've completed. + CrossOriginTextHtmlResponseSet& cross_origin_text_html_response_set = + *GetCrossOriginTextHtmlResponseSet(); + CrossOriginTextHtmlResponseSet::iterator request_iter = + cross_origin_text_html_response_set.find(response_url.spec()); + if (request_iter != cross_origin_text_html_response_set.end()) { + LogMislabeledTextHtmlResponse(); + cross_origin_text_html_response_set.erase(request_iter); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/site_isolation_metrics.h b/webkit/glue/site_isolation_metrics.h new file mode 100644 index 0000000..53f1630 --- /dev/null +++ b/webkit/glue/site_isolation_metrics.h @@ -0,0 +1,41 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_SITE_ISOLATION_METRICS_H_ +#define WEBKIT_GLUE_SITE_ISOLATION_METRICS_H_ + +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" + +namespace WebKit { +class WebFrame; +class WebURL; +class WebURLResponse; +} + +namespace webkit_glue { + +// Metrics to check the feasability of blocking cross-site requests that +// a renderer shouldn't be making (in case we try to move cross-site frames +// into their own process someday). We're erring on the side of counting more +// mime-types then we strictly need (we'd only consider blocking cross-site +// requests with types similar to HTML, XML, or JSON). +class SiteIsolationMetrics { + public: + static void AddRequest(unsigned identifier, + WebKit::WebURLRequest::TargetType target_type); + static bool AllowedByAccessControlHeader( + WebKit::WebFrame* frame, const WebKit::WebURLResponse& response); + static void LogMimeTypeForCrossOriginRequest( + WebKit::WebFrame* frame, + unsigned identifier, + const WebKit::WebURLResponse& response); + static void SniffCrossOriginHTML(const WebKit::WebURL& response_url, + const char* data, + int len); + static void RemoveCompletedResponse(const WebKit::WebURL& response_url); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_SITE_ISOLATION_METRICS_H_ diff --git a/webkit/glue/unittest_test_server.h b/webkit/glue/unittest_test_server.h new file mode 100644 index 0000000..0b3c7d2 --- /dev/null +++ b/webkit/glue/unittest_test_server.h @@ -0,0 +1,65 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_UNITTEST_TEST_SERVER_H__ +#define WEBKIT_GLUE_UNITTEST_TEST_SERVER_H__ + +#include "webkit/appcache/appcache_interfaces.h" +#include "webkit/glue/resource_loader_bridge.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_request_unittest.h" + +using webkit_glue::ResourceLoaderBridge; + +// We need to use ResourceLoaderBridge to communicate with the testserver +// instead of using URLRequest directly because URLRequests need to be run on +// the test_shell's IO thread. +class UnittestTestServer : public HTTPTestServer { + protected: + UnittestTestServer() { + } + + public: + static UnittestTestServer* CreateServer() { + UnittestTestServer* test_server = new UnittestTestServer(); + FilePath no_cert; + FilePath docroot(FILE_PATH_LITERAL("webkit/data")); + if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, + "localhost", 1337, docroot, no_cert, std::wstring())) { + delete test_server; + return NULL; + } + return test_server; + } + + virtual bool MakeGETRequest(const std::string& page_name) { + GURL url(TestServerPage(page_name)); + webkit_glue::ResourceLoaderBridge::RequestInfo request_info; + request_info.method = "GET"; + request_info.url = url; + request_info.first_party_for_cookies = url; + request_info.referrer = GURL(); // No referrer. + request_info.frame_origin = "null"; + request_info.main_frame_origin = "null"; + request_info.headers = std::string(); // No extra headers. + request_info.load_flags = net::LOAD_NORMAL; + request_info.requestor_pid = 0; + request_info.request_type = ResourceType::SUB_RESOURCE; + request_info.request_context = 0; + request_info.appcache_host_id = appcache::kNoHostId; + request_info.routing_id = 0; + scoped_ptr<ResourceLoaderBridge> loader( + ResourceLoaderBridge::Create(request_info)); + EXPECT_TRUE(loader.get()); + + ResourceLoaderBridge::SyncLoadResponse resp; + loader->SyncLoad(&resp); + return resp.status.is_success(); + } + + private: + virtual ~UnittestTestServer() {} +}; + +#endif // WEBKIT_GLUE_UNITTEST_TEST_SERVER_H__ diff --git a/webkit/glue/webaccessibility.cc b/webkit/glue/webaccessibility.cc new file mode 100644 index 0000000..b6e0d5e --- /dev/null +++ b/webkit/glue/webaccessibility.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2010 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 "webkit/glue/webaccessibility.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityCache.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityObject.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityRole.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" + +using WebKit::WebAccessibilityCache; +using WebKit::WebAccessibilityRole; +using WebKit::WebAccessibilityObject; + +namespace webkit_glue { + +// Provides a conversion between the WebKit::WebAccessibilityRole and a role +// supported on the Browser side. Listed alphabetically by the +// WebAccessibilityRole (except for default role). +WebAccessibility::Role ConvertRole(WebKit::WebAccessibilityRole role) { + switch (role) { + case WebKit::WebAccessibilityRoleAnnotation: + return WebAccessibility::ROLE_ANNOTATION; + case WebKit::WebAccessibilityRoleApplication: + return WebAccessibility::ROLE_APPLICATION; + case WebKit::WebAccessibilityRoleApplicationAlert: + return WebAccessibility::ROLE_ALERT; + case WebKit::WebAccessibilityRoleApplicationAlertDialog: + return WebAccessibility::ROLE_ALERT_DIALOG; + case WebKit::WebAccessibilityRoleApplicationDialog: + return WebAccessibility::ROLE_DIALOG; + case WebKit::WebAccessibilityRoleApplicationLog: + return WebAccessibility::ROLE_LOG; + case WebKit::WebAccessibilityRoleApplicationMarquee: + return WebAccessibility::ROLE_MARQUEE; + case WebKit::WebAccessibilityRoleApplicationStatus: + return WebAccessibility::ROLE_STATUS; + case WebKit::WebAccessibilityRoleApplicationTimer: + return WebAccessibility::ROLE_TIMER; + case WebKit::WebAccessibilityRoleBrowser: + return WebAccessibility::ROLE_BROWSER; + case WebKit::WebAccessibilityRoleBusyIndicator: + return WebAccessibility::ROLE_BUSY_INDICATOR; + case WebKit::WebAccessibilityRoleButton: + return WebAccessibility::ROLE_BUTTON; + case WebKit::WebAccessibilityRoleCell: + return WebAccessibility::ROLE_CELL; + case WebKit::WebAccessibilityRoleCheckBox: + return WebAccessibility::ROLE_CHECKBOX; + case WebKit::WebAccessibilityRoleColorWell: + return WebAccessibility::ROLE_COLOR_WELL; + case WebKit::WebAccessibilityRoleColumn: + return WebAccessibility::ROLE_COLUMN; + case WebKit::WebAccessibilityRoleColumnHeader: + return WebAccessibility::ROLE_COLUMN_HEADER; + case WebKit::WebAccessibilityRoleComboBox: + return WebAccessibility::ROLE_COMBO_BOX; + case WebKit::WebAccessibilityRoleDefinitionListDefinition: + return WebAccessibility::ROLE_DEFINITION_LIST_DEFINITION; + case WebKit::WebAccessibilityRoleDefinitionListTerm: + return WebAccessibility::ROLE_DEFINITION_LIST_TERM; + case WebKit::WebAccessibilityRoleDirectory: + return WebAccessibility::ROLE_DIRECTORY; + case WebKit::WebAccessibilityRoleDisclosureTriangle: + return WebAccessibility::ROLE_DISCLOSURE_TRIANGLE; + case WebKit::WebAccessibilityRoleDocument: + return WebAccessibility::ROLE_DOCUMENT; + case WebKit::WebAccessibilityRoleDocumentArticle: + return WebAccessibility::ROLE_ARTICLE; + case WebKit::WebAccessibilityRoleDocumentMath: + return WebAccessibility::ROLE_MATH; + case WebKit::WebAccessibilityRoleDocumentNote: + return WebAccessibility::ROLE_NOTE; + case WebKit::WebAccessibilityRoleDocumentRegion: + return WebAccessibility::ROLE_REGION; + case WebKit::WebAccessibilityRoleDrawer: + return WebAccessibility::ROLE_DRAWER; + case WebKit::WebAccessibilityRoleEditableText: + return WebAccessibility::ROLE_EDITABLE_TEXT; + case WebKit::WebAccessibilityRoleGrid: + return WebAccessibility::ROLE_GRID; + case WebKit::WebAccessibilityRoleGroup: + return WebAccessibility::ROLE_GROUP; + case WebKit::WebAccessibilityRoleGrowArea: + return WebAccessibility::ROLE_GROW_AREA; + case WebKit::WebAccessibilityRoleHeading: + return WebAccessibility::ROLE_HEADING; + case WebKit::WebAccessibilityRoleHelpTag: + return WebAccessibility::ROLE_HELP_TAG; + case WebKit::WebAccessibilityRoleIgnored: + return WebAccessibility::ROLE_IGNORED; + case WebKit::WebAccessibilityRoleImage: + return WebAccessibility::ROLE_IMAGE; + case WebKit::WebAccessibilityRoleImageMap: + return WebAccessibility::ROLE_IMAGE_MAP; + case WebKit::WebAccessibilityRoleImageMapLink: + return WebAccessibility::ROLE_IMAGE_MAP_LINK; + case WebKit::WebAccessibilityRoleIncrementor: + return WebAccessibility::ROLE_INCREMENTOR; + case WebKit::WebAccessibilityRoleLandmarkApplication: + return WebAccessibility::ROLE_LANDMARK_APPLICATION; + case WebKit::WebAccessibilityRoleLandmarkBanner: + return WebAccessibility::ROLE_LANDMARK_BANNER; + case WebKit::WebAccessibilityRoleLandmarkComplementary: + return WebAccessibility::ROLE_LANDMARK_COMPLEMENTARY; + case WebKit::WebAccessibilityRoleLandmarkContentInfo: + return WebAccessibility::ROLE_LANDMARK_CONTENTINFO; + case WebKit::WebAccessibilityRoleLandmarkMain: + return WebAccessibility::ROLE_LANDMARK_MAIN; + case WebKit::WebAccessibilityRoleLandmarkNavigation: + return WebAccessibility::ROLE_LANDMARK_NAVIGATION; + case WebKit::WebAccessibilityRoleLandmarkSearch: + return WebAccessibility::ROLE_LANDMARK_SEARCH; + case WebKit::WebAccessibilityRoleLink: + return WebAccessibility::ROLE_LINK; + case WebKit::WebAccessibilityRoleList: + return WebAccessibility::ROLE_LIST; + case WebKit::WebAccessibilityRoleListBox: + return WebAccessibility::ROLE_LISTBOX; + case WebKit::WebAccessibilityRoleListBoxOption: + return WebAccessibility::ROLE_LISTBOX_OPTION; + case WebKit::WebAccessibilityRoleListItem: + return WebAccessibility::ROLE_LIST_ITEM; + case WebKit::WebAccessibilityRoleListMarker: + return WebAccessibility::ROLE_LIST_MARKER; + case WebKit::WebAccessibilityRoleMatte: + return WebAccessibility::ROLE_MATTE; + case WebKit::WebAccessibilityRoleMenu: + return WebAccessibility::ROLE_MENU; + case WebKit::WebAccessibilityRoleMenuBar: + return WebAccessibility::ROLE_MENU_BAR; + case WebKit::WebAccessibilityRoleMenuButton: + return WebAccessibility::ROLE_MENU_BUTTON; + case WebKit::WebAccessibilityRoleMenuItem: + return WebAccessibility::ROLE_MENU_ITEM; + case WebKit::WebAccessibilityRoleMenuListOption: + return WebAccessibility::ROLE_MENU_LIST_OPTION; + case WebKit::WebAccessibilityRoleMenuListPopup: + return WebAccessibility::ROLE_MENU_LIST_POPUP; + case WebKit::WebAccessibilityRoleOutline: + return WebAccessibility::ROLE_OUTLINE; + case WebKit::WebAccessibilityRolePopUpButton: + return WebAccessibility::ROLE_POPUP_BUTTON; + case WebKit::WebAccessibilityRoleProgressIndicator: + return WebAccessibility::ROLE_PROGRESS_INDICATOR; + case WebKit::WebAccessibilityRoleRadioButton: + return WebAccessibility::ROLE_RADIO_BUTTON; + case WebKit::WebAccessibilityRoleRadioGroup: + return WebAccessibility::ROLE_RADIO_GROUP; + case WebKit::WebAccessibilityRoleRow: + return WebAccessibility::ROLE_ROW; + case WebKit::WebAccessibilityRoleRowHeader: + return WebAccessibility::ROLE_ROW_HEADER; + case WebKit::WebAccessibilityRoleRuler: + return WebAccessibility::ROLE_RULER; + case WebKit::WebAccessibilityRoleRulerMarker: + return WebAccessibility::ROLE_RULER_MARKER; + case WebKit::WebAccessibilityRoleScrollArea: + return WebAccessibility::ROLE_SCROLLAREA; + case WebKit::WebAccessibilityRoleScrollBar: + return WebAccessibility::ROLE_SCROLLBAR; + case WebKit::WebAccessibilityRoleSheet: + return WebAccessibility::ROLE_SHEET; + case WebKit::WebAccessibilityRoleSlider: + return WebAccessibility::ROLE_SLIDER; + case WebKit::WebAccessibilityRoleSliderThumb: + return WebAccessibility::ROLE_SLIDER_THUMB; + case WebKit::WebAccessibilityRoleSplitGroup: + return WebAccessibility::ROLE_SPLIT_GROUP; + case WebKit::WebAccessibilityRoleSplitter: + return WebAccessibility::ROLE_SPLITTER; + case WebKit::WebAccessibilityRoleStaticText: + return WebAccessibility::ROLE_STATIC_TEXT; + case WebKit::WebAccessibilityRoleSystemWide: + return WebAccessibility::ROLE_SYSTEM_WIDE; + case WebKit::WebAccessibilityRoleTab: + return WebAccessibility::ROLE_TAB; + case WebKit::WebAccessibilityRoleTabGroup: + return WebAccessibility::ROLE_TAB_GROUP; + case WebKit::WebAccessibilityRoleTabList: + return WebAccessibility::ROLE_TAB_LIST; + case WebKit::WebAccessibilityRoleTabPanel: + return WebAccessibility::ROLE_TAB_PANEL; + case WebKit::WebAccessibilityRoleTable: + return WebAccessibility::ROLE_TABLE; + case WebKit::WebAccessibilityRoleTableHeaderContainer: + return WebAccessibility::ROLE_TABLE_HEADER_CONTAINER; + case WebKit::WebAccessibilityRoleTextArea: + return WebAccessibility::ROLE_TEXTAREA; + case WebKit::WebAccessibilityRoleTextField: + return WebAccessibility::ROLE_TEXT_FIELD; + case WebKit::WebAccessibilityRoleToolbar: + return WebAccessibility::ROLE_TOOLBAR; + case WebKit::WebAccessibilityRoleTreeGrid: + return WebAccessibility::ROLE_TREE_GRID; + case WebKit::WebAccessibilityRoleTreeItemRole: + return WebAccessibility::ROLE_TREE_ITEM; + case WebKit::WebAccessibilityRoleTreeRole: + return WebAccessibility::ROLE_TREE; + case WebKit::WebAccessibilityRoleUserInterfaceTooltip: + return WebAccessibility::ROLE_TOOLTIP; + case WebKit::WebAccessibilityRoleValueIndicator: + return WebAccessibility::ROLE_VALUE_INDICATOR; + case WebKit::WebAccessibilityRoleWebArea: + return WebAccessibility::ROLE_WEB_AREA; + case WebKit::WebAccessibilityRoleWebCoreLink: + return WebAccessibility::ROLE_WEBCORE_LINK; + case WebKit::WebAccessibilityRoleWindow: + return WebAccessibility::ROLE_WINDOW; + + default: + return WebAccessibility::ROLE_UNKNOWN; + } +} + +uint32 ConvertState(const WebAccessibilityObject& o) { + uint32 state = 0; + if (o.isChecked()) + state |= (1 << WebAccessibility::STATE_CHECKED); + + if (o.canSetFocusAttribute()) + state |= (1 << WebAccessibility::STATE_FOCUSABLE); + + if (o.isFocused()) + state |= (1 << WebAccessibility::STATE_FOCUSED); + + if (o.isHovered()) + state |= (1 << WebAccessibility::STATE_HOTTRACKED); + + if (o.isIndeterminate()) + state |= (1 << WebAccessibility::STATE_INDETERMINATE); + + if (o.isAnchor()) + state |= (1 << WebAccessibility::STATE_LINKED); + + if (o.isMultiSelectable()) + state |= (1 << WebAccessibility::STATE_MULTISELECTABLE); + + if (o.isOffScreen()) + state |= (1 << WebAccessibility::STATE_OFFSCREEN); + + if (o.isPressed()) + state |= (1 << WebAccessibility::STATE_PRESSED); + + if (o.isPasswordField()) + state |= (1 << WebAccessibility::STATE_PROTECTED); + + if (o.isReadOnly()) + state |= (1 << WebAccessibility::STATE_READONLY); + + if (o.isVisited()) + state |= (1 << WebAccessibility::STATE_TRAVERSED); + + if (!o.isEnabled()) + state |= (1 << WebAccessibility::STATE_UNAVAILABLE); + + return state; +} + +WebAccessibility::WebAccessibility() + : id(-1), + role(ROLE_NONE), + state(-1) { +} + +WebAccessibility::WebAccessibility(const WebKit::WebAccessibilityObject& src, + WebKit::WebAccessibilityCache* cache) { + Init(src, cache); +} + +void WebAccessibility::Init(const WebKit::WebAccessibilityObject& src, + WebKit::WebAccessibilityCache* cache) { + name = src.title(); + value = src.stringValue(); + role = ConvertRole(src.roleValue()); + state = ConvertState(src); + location = src.boundingBoxRect(); + + if (src.actionVerb().length()) + attributes[ATTR_ACTION] = src.actionVerb(); + if (src.accessibilityDescription().length()) + attributes[ATTR_DESCRIPTION] = src.accessibilityDescription(); + if (src.helpText().length()) + attributes[ATTR_HELP] = src.helpText(); + if (src.keyboardShortcut().length()) + attributes[ATTR_SHORTCUT] = src.keyboardShortcut(); + + // Add the source object to the cache and store its id. + id = cache->addOrGetId(src); + + // Recursively create children. + int child_count = src.childCount(); + children.resize(child_count); + for (int i = 0; i < child_count; i++) { + children[i].Init(src.childAt(i), cache); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/webaccessibility.h b/webkit/glue/webaccessibility.h new file mode 100644 index 0000000..305bc0c --- /dev/null +++ b/webkit/glue/webaccessibility.h @@ -0,0 +1,187 @@ +// Copyright (c) 2010 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 WEBKIT_GLUE_WEBACCESSIBILITY_H_ +#define WEBKIT_GLUE_WEBACCESSIBILITY_H_ + +#include <map> +#include <vector> + +#include "base/string16.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityObject.h" +#include "third_party/WebKit/WebKit/chromium/public/WebAccessibilityRole.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" + +namespace WebKit { +class WebAccessibilityCache; +} + +namespace webkit_glue { + +// A compact representation of the accessibility information for a +// single web object, in a form that can be serialized and sent from +// the renderer process to the browser process. +struct WebAccessibility { + public: + // An alphabetical enumeration of accessibility roles. + enum Role { + ROLE_NONE = 0, + + ROLE_UNKNOWN, + + ROLE_ALERT, + ROLE_ALERT_DIALOG, + ROLE_ANNOTATION, + ROLE_APPLICATION, + ROLE_ARTICLE, + ROLE_BROWSER, + ROLE_BUSY_INDICATOR, + ROLE_BUTTON, + ROLE_CELL, + ROLE_CHECKBOX, + ROLE_COLOR_WELL, + ROLE_COLUMN, + ROLE_COLUMN_HEADER, + ROLE_COMBO_BOX, + ROLE_DEFINITION_LIST_DEFINITION, + ROLE_DEFINITION_LIST_TERM, + ROLE_DIALOG, + ROLE_DIRECTORY, + ROLE_DISCLOSURE_TRIANGLE, + ROLE_DOCUMENT, + ROLE_DRAWER, + ROLE_EDITABLE_TEXT, + ROLE_GRID, + ROLE_GROUP, + ROLE_GROW_AREA, + ROLE_HEADING, + ROLE_HELP_TAG, + ROLE_IGNORED, + ROLE_IMAGE, + ROLE_IMAGE_MAP, + ROLE_IMAGE_MAP_LINK, + ROLE_INCREMENTOR, + ROLE_LANDMARK_APPLICATION, + ROLE_LANDMARK_BANNER, + ROLE_LANDMARK_COMPLEMENTARY, + ROLE_LANDMARK_CONTENTINFO, + ROLE_LANDMARK_MAIN, + ROLE_LANDMARK_NAVIGATION, + ROLE_LANDMARK_SEARCH, + ROLE_LINK, + ROLE_LIST, + ROLE_LISTBOX, + ROLE_LISTBOX_OPTION, + ROLE_LIST_ITEM, + ROLE_LIST_MARKER, + ROLE_LOG, + ROLE_MARQUEE, + ROLE_MATH, + ROLE_MATTE, + ROLE_MENU, + ROLE_MENU_BAR, + ROLE_MENU_ITEM, + ROLE_MENU_BUTTON, + ROLE_MENU_LIST_OPTION, + ROLE_MENU_LIST_POPUP, + ROLE_NOTE, + ROLE_OUTLINE, + ROLE_POPUP_BUTTON, + ROLE_PROGRESS_INDICATOR, + ROLE_RADIO_BUTTON, + ROLE_RADIO_GROUP, + ROLE_REGION, + ROLE_ROW, + ROLE_ROW_HEADER, + ROLE_RULER, + ROLE_RULER_MARKER, + ROLE_SCROLLAREA, + ROLE_SCROLLBAR, + ROLE_SHEET, + ROLE_SLIDER, + ROLE_SLIDER_THUMB, + ROLE_SPLITTER, + ROLE_SPLIT_GROUP, + ROLE_STATIC_TEXT, + ROLE_STATUS, + ROLE_SYSTEM_WIDE, + ROLE_TAB, + ROLE_TABLE, + ROLE_TABLE_HEADER_CONTAINER, + ROLE_TAB_GROUP, + ROLE_TAB_LIST, + ROLE_TAB_PANEL, + ROLE_TEXTAREA, + ROLE_TEXT_FIELD, + ROLE_TIMER, + ROLE_TOOLBAR, + ROLE_TOOLTIP, + ROLE_TREE, + ROLE_TREE_GRID, + ROLE_TREE_ITEM, + ROLE_VALUE_INDICATOR, + ROLE_WEBCORE_LINK, + ROLE_WEB_AREA, + ROLE_WINDOW, + NUM_ROLES + }; + + // An alphabetical enumeration of accessibility states. + // A state bitmask is formed by shifting 1 to the left by each state, + // for example: + // int mask = (1 << STATE_CHECKED) | (1 << STATE_FOCUSED); + enum State { + STATE_CHECKED, + STATE_FOCUSABLE, + STATE_FOCUSED, + STATE_HOTTRACKED, + STATE_INDETERMINATE, + STATE_LINKED, + STATE_MULTISELECTABLE, + STATE_OFFSCREEN, + STATE_PRESSED, + STATE_PROTECTED, + STATE_READONLY, + STATE_TRAVERSED, + STATE_UNAVAILABLE + }; + + enum Attribute { + ATTR_ACTION, + ATTR_DESCRIPTION, + ATTR_HELP, + ATTR_HTML_TAG, + ATTR_LINK_TARGET, + ATTR_SHORTCUT, + NUM_ATTRIBUTES + }; + + // Empty constructor, for serialization. + WebAccessibility(); + + // Construct from a WebAccessibilityObject. Recursively creates child + // nodes as needed to complete the tree. Adds |src| to |cache| and + // stores its cache ID. + WebAccessibility(const WebKit::WebAccessibilityObject& src, + WebKit::WebAccessibilityCache* cache); + + // Initialize an already-created struct, same as the constructor a + void Init(const WebKit::WebAccessibilityObject& src, + WebKit::WebAccessibilityCache* cache); + + // This is a simple serializable struct. All member variables should be + // copyable. + int32 id; + string16 name; + string16 value; + Role role; + uint32 state; + WebKit::WebRect location; + std::map<int32, string16> attributes; + std::vector<WebAccessibility> children; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBACCESSIBILITY_H_ diff --git a/webkit/glue/webclipboard_impl.cc b/webkit/glue/webclipboard_impl.cc new file mode 100644 index 0000000..11bc96d --- /dev/null +++ b/webkit/glue/webclipboard_impl.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#include "webkit/glue/webclipboard_impl.h" + +#include "app/clipboard/clipboard.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "webkit/glue/scoped_clipboard_writer_glue.h" +#include "webkit/glue/webkit_glue.h" + +#if WEBKIT_USING_CG +#include "skia/ext/skia_utils_mac.h" +#endif + +using WebKit::WebClipboard; +using WebKit::WebImage; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebVector; + +namespace webkit_glue { + +// Static +std::string WebClipboardImpl::URLToMarkup(const WebURL& url, + const WebString& title) { + std::string markup("<a href=\""); + markup.append(url.spec()); + markup.append("\">"); + // TODO(darin): HTML escape this + markup.append(EscapeForHTML(UTF16ToUTF8(title))); + markup.append("</a>"); + return markup; +} + +// Static +std::string WebClipboardImpl::URLToImageMarkup(const WebURL& url, + const WebString& title) { + std::string markup("<img src=\""); + markup.append(url.spec()); + markup.append("\""); + if (!title.isEmpty()) { + markup.append(" alt=\""); + markup.append(EscapeForHTML(UTF16ToUTF8(title))); + markup.append("\""); + } + markup.append("/>"); + return markup; +} + +bool WebClipboardImpl::isFormatAvailable(Format format, Buffer buffer) { + Clipboard::FormatType format_type; + Clipboard::Buffer buffer_type; + + switch (format) { + case FormatHTML: + format_type = Clipboard::GetHtmlFormatType(); + break; + case FormatSmartPaste: + format_type = Clipboard::GetWebKitSmartPasteFormatType(); + break; + case FormatBookmark: +#if defined(OS_WIN) || defined(OS_MACOSX) + format_type = Clipboard::GetUrlWFormatType(); + break; +#endif + default: + NOTREACHED(); + return false; + } + + if (!ConvertBufferType(buffer, &buffer_type)) + return false; + + return ClipboardIsFormatAvailable(format_type, buffer_type); +} + +WebString WebClipboardImpl::readPlainText(Buffer buffer) { + Clipboard::Buffer buffer_type; + if (!ConvertBufferType(buffer, &buffer_type)) + return WebString(); + + if (ClipboardIsFormatAvailable(Clipboard::GetPlainTextWFormatType(), + buffer_type)) { + string16 text; + ClipboardReadText(buffer_type, &text); + if (!text.empty()) + return text; + } + + if (ClipboardIsFormatAvailable(Clipboard::GetPlainTextFormatType(), + buffer_type)) { + std::string text; + ClipboardReadAsciiText(buffer_type, &text); + if (!text.empty()) + return ASCIIToUTF16(text); + } + + return WebString(); +} + +WebString WebClipboardImpl::readHTML(Buffer buffer, WebURL* source_url) { + Clipboard::Buffer buffer_type; + if (!ConvertBufferType(buffer, &buffer_type)) + return WebString(); + + string16 html_stdstr; + GURL gurl; + ClipboardReadHTML(buffer_type, &html_stdstr, &gurl); + *source_url = gurl; + return html_stdstr; +} + +void WebClipboardImpl::writeHTML( + const WebString& html_text, const WebURL& source_url, + const WebString& plain_text, bool write_smart_paste) { + ScopedClipboardWriterGlue scw(ClipboardGetClipboard()); + scw.WriteHTML(html_text, source_url.spec()); + scw.WriteText(plain_text); + + if (write_smart_paste) + scw.WriteWebSmartPaste(); +} + +void WebClipboardImpl::writePlainText(const WebString& plain_text) { + ScopedClipboardWriterGlue scw(ClipboardGetClipboard()); + scw.WriteText(plain_text); +} + +void WebClipboardImpl::writeURL(const WebURL& url, const WebString& title) { + ScopedClipboardWriterGlue scw(ClipboardGetClipboard()); + + scw.WriteBookmark(title, url.spec()); + scw.WriteHTML(UTF8ToUTF16(URLToMarkup(url, title)), ""); + scw.WriteText(UTF8ToUTF16(url.spec())); +} + +void WebClipboardImpl::writeImage( + const WebImage& image, const WebURL& url, const WebString& title) { + ScopedClipboardWriterGlue scw(ClipboardGetClipboard()); + + if (!image.isNull()) { +#if WEBKIT_USING_SKIA + const SkBitmap& bitmap = image.getSkBitmap(); +#elif WEBKIT_USING_CG + const SkBitmap& bitmap = gfx::CGImageToSkBitmap(image.getCGImageRef()); +#endif + SkAutoLockPixels locked(bitmap); + scw.WriteBitmapFromPixels(bitmap.getPixels(), image.size()); + } + + // When writing the image, we also write the image markup so that pasting + // into rich text editors, such as Gmail, reveals the image. We also don't + // want to call writeText(), since some applications (WordPad) don't pick the + // image if there is also a text format on the clipboard. + if (!url.isEmpty()) { + scw.WriteBookmark(title, url.spec()); + scw.WriteHTML(UTF8ToUTF16(URLToImageMarkup(url, title)), ""); + } +} + +void WebClipboardImpl::writeData(const WebKit::WebDragData& data) { + // TODO(dcheng): Implement this stub. +} + +WebVector<WebString> WebClipboardImpl::readAvailableTypes( + Buffer buffer, bool* contains_filenames) { + Clipboard::Buffer buffer_type; + std::vector<string16> types; + if (ConvertBufferType(buffer, &buffer_type)) { + ClipboardReadAvailableTypes(buffer_type, &types, contains_filenames); + } + return types; +} + +bool WebClipboardImpl::readData(Buffer buffer, const WebString& type, + WebString* data, WebString* metadata) { + Clipboard::Buffer buffer_type; + if (!ConvertBufferType(buffer, &buffer_type)) + return false; + + string16 data_out; + string16 metadata_out; + bool result = ClipboardReadData(buffer_type, type, &data_out, &metadata_out); + if (result) { + *data = data_out; + *metadata = metadata_out; + } + return result; +} + +WebVector<WebString> WebClipboardImpl::readFilenames(Buffer buffer) { + Clipboard::Buffer buffer_type; + std::vector<string16> filenames; + if (ConvertBufferType(buffer, &buffer_type)) { + ClipboardReadFilenames(buffer_type, &filenames); + } + return filenames; +} + +bool WebClipboardImpl::ConvertBufferType(Buffer buffer, + Clipboard::Buffer* result) { + switch (buffer) { + case BufferStandard: + *result = Clipboard::BUFFER_STANDARD; + break; + case BufferDrag: + *result = Clipboard::BUFFER_DRAG; + case BufferSelection: +#if defined(USE_X11) + *result = Clipboard::BUFFER_SELECTION; + break; +#endif + default: + NOTREACHED(); + return false; + } + return true; +} + +} // namespace webkit_glue diff --git a/webkit/glue/webclipboard_impl.h b/webkit/glue/webclipboard_impl.h new file mode 100644 index 0000000..92c4a5e --- /dev/null +++ b/webkit/glue/webclipboard_impl.h @@ -0,0 +1,55 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#ifndef WEBCLIPBOARD_IMPL_H_ +#define WEBCLIPBOARD_IMPL_H_ + +#include "app/clipboard/clipboard.h" +#include "third_party/WebKit/WebKit/chromium/public/WebClipboard.h" + +#include <string> + +namespace webkit_glue { + +class WebClipboardImpl : public WebKit::WebClipboard { + public: + static std::string URLToMarkup(const WebKit::WebURL& url, + const WebKit::WebString& title); + static std::string URLToImageMarkup(const WebKit::WebURL& url, + const WebKit::WebString& title); + + virtual ~WebClipboardImpl() {} + + // WebClipboard methods: + virtual bool isFormatAvailable(Format, Buffer); + virtual WebKit::WebString readPlainText(Buffer); + virtual WebKit::WebString readHTML(Buffer, WebKit::WebURL* source_url); + virtual void writeHTML( + const WebKit::WebString& html_text, + const WebKit::WebURL& source_url, + const WebKit::WebString& plain_text, + bool write_smart_paste); + virtual void writePlainText(const WebKit::WebString& plain_text); + virtual void writeURL( + const WebKit::WebURL&, + const WebKit::WebString& title); + virtual void writeImage( + const WebKit::WebImage&, + const WebKit::WebURL& source_url, + const WebKit::WebString& title); + virtual void writeData(const WebKit::WebDragData&); + + virtual WebKit::WebVector<WebKit::WebString> readAvailableTypes( + Buffer, bool* contains_filenames); + virtual bool readData(Buffer, const WebKit::WebString& type, + WebKit::WebString* data, WebKit::WebString* metadata); + virtual WebKit::WebVector<WebKit::WebString> readFilenames(Buffer); + + private: + bool ConvertBufferType(Buffer, Clipboard::Buffer*); +}; + +} // namespace webkit_glue + +#endif // WEBCLIPBOARD_IMPL_H_ diff --git a/webkit/glue/webcookie.h b/webkit/glue/webcookie.h new file mode 100644 index 0000000..e2f11e2 --- /dev/null +++ b/webkit/glue/webcookie.h @@ -0,0 +1,77 @@ +// Copyright (c) 2006-2008 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. +// +// A struct for managing data being dropped on a webview. This represents a +// union of all the types of data that can be dropped in a platform neutral +// way. + +#ifndef WEBKIT_GLUE_WEBCOOKIE_H_ +#define WEBKIT_GLUE_WEBCOOKIE_H_ + +#include "net/base/cookie_monster.h" + +namespace webkit_glue { + +struct WebCookie { + + WebCookie(const std::string& name, const std::string& value, + const std::string& domain, const std::string& path, double expires, + bool http_only, bool secure, bool session) + : name(name), + value(value), + domain(domain), + path(path), + expires(expires), + http_only(http_only), + secure(secure), + session(session) { + } + + explicit WebCookie(const net::CookieMonster::CanonicalCookie& c) + : name(c.Name()), + value(c.Value()), + domain(c.Domain()), + path(c.Path()), + expires(c.ExpiryDate().ToDoubleT() * 1000), + http_only(c.IsHttpOnly()), + secure(c.IsSecure()), + session(!c.IsPersistent()) { + } + + // For default constructions. + WebCookie() + : expires(0), + http_only(false), + secure(false), + session(false) { + } + + // Cookie name. + std::string name; + + // Cookie value. + std::string value; + + // Cookie domain. + std::string domain; + + // Cookie path. + std::string path; + + // Cookie expires param if any. + double expires; + + // Cookie HTTPOnly param. + bool http_only; + + // Cookie secure param. + bool secure; + + // Session cookie flag. + bool session; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBCOOKIE_H_ diff --git a/webkit/glue/webcursor.cc b/webkit/glue/webcursor.cc new file mode 100644 index 0000000..f09372c --- /dev/null +++ b/webkit/glue/webcursor.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/webcursor.h" + +#include "base/logging.h" +#include "base/pickle.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" + +using WebKit::WebCursorInfo; +using WebKit::WebImage; + +static const int kMaxCursorDimension = 1024; + +WebCursor::WebCursor() + : type_(WebCursorInfo::TypePointer) { + InitPlatformData(); +} + +WebCursor::WebCursor(const WebCursorInfo& cursor_info) + : type_(WebCursorInfo::TypePointer) { + InitPlatformData(); + InitFromCursorInfo(cursor_info); +} + +WebCursor::~WebCursor() { + Clear(); +} + +WebCursor::WebCursor(const WebCursor& other) { + InitPlatformData(); + Copy(other); +} + +const WebCursor& WebCursor::operator=(const WebCursor& other) { + if (this == &other) + return *this; + + Clear(); + Copy(other); + return *this; +} + +void WebCursor::InitFromCursorInfo(const WebCursorInfo& cursor_info) { + Clear(); + +#if defined(OS_WIN) + if (cursor_info.externalHandle) { + InitFromExternalCursor(cursor_info.externalHandle); + return; + } +#endif + + type_ = cursor_info.type; + hotspot_ = cursor_info.hotSpot; + if (IsCustom()) + SetCustomData(cursor_info.customImage); +} + +void WebCursor::GetCursorInfo(WebCursorInfo* cursor_info) const { + cursor_info->type = static_cast<WebCursorInfo::Type>(type_); + cursor_info->hotSpot = hotspot_; + ImageFromCustomData(&cursor_info->customImage); + +#if defined(OS_WIN) + cursor_info->externalHandle = external_cursor_; +#endif +} + +bool WebCursor::Deserialize(const Pickle* pickle, void** iter) { + int type, hotspot_x, hotspot_y, size_x, size_y, data_len; + + const char* data; + + // Leave |this| unmodified unless we are going to return success. + if (!pickle->ReadInt(iter, &type) || + !pickle->ReadInt(iter, &hotspot_x) || + !pickle->ReadInt(iter, &hotspot_y) || + !pickle->ReadLength(iter, &size_x) || + !pickle->ReadLength(iter, &size_y) || + !pickle->ReadData(iter, &data, &data_len)) + return false; + + // Ensure the size is sane, and there is enough data. + if (size_x > kMaxCursorDimension || + size_y > kMaxCursorDimension) + return false; + + if (type == WebCursorInfo::TypeCustom && (size_x == 0 || size_y == 0)) + return false; + + // The * 4 is because the expected format is an array of RGBA pixel values. + if (size_x * size_y * 4 > data_len) + return false; + + type_ = type; + hotspot_.set_x(hotspot_x); + hotspot_.set_y(hotspot_y); + custom_size_.set_width(size_x); + custom_size_.set_height(size_y); + + custom_data_.clear(); + if (data_len > 0) { + custom_data_.resize(data_len); + memcpy(&custom_data_[0], data, data_len); + } + + return DeserializePlatformData(pickle, iter); +} + +bool WebCursor::Serialize(Pickle* pickle) const { + if (!pickle->WriteInt(type_) || + !pickle->WriteInt(hotspot_.x()) || + !pickle->WriteInt(hotspot_.y()) || + !pickle->WriteInt(custom_size_.width()) || + !pickle->WriteInt(custom_size_.height())) + return false; + + const char* data = NULL; + if (!custom_data_.empty()) + data = &custom_data_[0]; + if (!pickle->WriteData(data, custom_data_.size())) + return false; + + return SerializePlatformData(pickle); +} + +bool WebCursor::IsCustom() const { + return type_ == WebCursorInfo::TypeCustom; +} + +bool WebCursor::IsEqual(const WebCursor& other) const { + if (type_ != other.type_) + return false; + + if (!IsPlatformDataEqual(other)) + return false; + + return hotspot_ == other.hotspot_ && + custom_size_ == other.custom_size_ && + custom_data_ == other.custom_data_; +} + +void WebCursor::Clear() { + type_ = WebCursorInfo::TypePointer; + hotspot_.set_x(0); + hotspot_.set_y(0); + custom_size_.set_width(0); + custom_size_.set_height(0); + custom_data_.clear(); + CleanupPlatformData(); +} + +void WebCursor::Copy(const WebCursor& other) { + type_ = other.type_; + hotspot_ = other.hotspot_; + custom_size_ = other.custom_size_; + custom_data_ = other.custom_data_; + CopyPlatformData(other); +} + +#if WEBKIT_USING_SKIA +// The WEBKIT_USING_CG implementation is in webcursor_mac.mm. +void WebCursor::SetCustomData(const WebImage& image) { + if (image.isNull()) + return; + + // Fill custom_data_ directly with the NativeImage pixels. + const SkBitmap& bitmap = image.getSkBitmap(); + SkAutoLockPixels bitmap_lock(bitmap); + custom_data_.resize(bitmap.getSize()); + if (!custom_data_.empty()) + memcpy(&custom_data_[0], bitmap.getPixels(), bitmap.getSize()); + custom_size_.set_width(bitmap.width()); + custom_size_.set_height(bitmap.height()); +} + +void WebCursor::ImageFromCustomData(WebImage* image) const { + if (custom_data_.empty()) + return; + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, + custom_size_.width(), + custom_size_.height()); + if (!bitmap.allocPixels()) + return; + memcpy(bitmap.getPixels(), &custom_data_[0], custom_data_.size()); + + image->assign(bitmap); +} +#endif diff --git a/webkit/glue/webcursor.h b/webkit/glue/webcursor.h new file mode 100644 index 0000000..293bd74 --- /dev/null +++ b/webkit/glue/webcursor.h @@ -0,0 +1,149 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_WEBCURSOR_H_ +#define WEBKIT_GLUE_WEBCURSOR_H_ + +#include "base/basictypes.h" +#include "gfx/point.h" +#include "gfx/size.h" + +#include <vector> + +#if defined(OS_WIN) +typedef struct HINSTANCE__* HINSTANCE; +typedef struct HICON__* HICON; +typedef HICON HCURSOR; +#elif defined(USE_X11) +typedef struct _GdkCursor GdkCursor; +#elif defined(OS_MACOSX) +#ifdef __OBJC__ +@class NSCursor; +#else +class NSCursor; +#endif +typedef UInt32 ThemeCursor; +struct Cursor; +#endif + +class Pickle; + +namespace WebKit { +class WebImage; +struct WebCursorInfo; +} + +// This class encapsulates a cross-platform description of a cursor. Platform +// specific methods are provided to translate the cross-platform cursor into a +// platform specific cursor. It is also possible to serialize / de-serialize a +// WebCursor. +class WebCursor { + public: + WebCursor(); + explicit WebCursor(const WebKit::WebCursorInfo& cursor_info); + ~WebCursor(); + + // Copy constructor/assignment operator combine. + WebCursor(const WebCursor& other); + const WebCursor& operator=(const WebCursor& other); + + // Conversion from/to WebCursorInfo. + void InitFromCursorInfo(const WebKit::WebCursorInfo& cursor_info); + void GetCursorInfo(WebKit::WebCursorInfo* cursor_info) const; + + // Serialization / De-serialization + bool Deserialize(const Pickle* pickle, void** iter); + bool Serialize(Pickle* pickle) const; + + // Returns true if GetCustomCursor should be used to allocate a platform + // specific cursor object. Otherwise GetCursor should be used. + bool IsCustom() const; + + // Returns true if the current cursor object contains the same cursor as the + // cursor object passed in. If the current cursor is a custom cursor, we also + // compare the bitmaps to verify whether they are equal. + bool IsEqual(const WebCursor& other) const; + +#if defined(OS_WIN) + // Returns a HCURSOR representing the current WebCursor instance. + // The ownership of the HCURSOR (does not apply to external cursors) remains + // with the WebCursor instance. + HCURSOR GetCursor(HINSTANCE module_handle); + + // Initialize this from the given Windows cursor. The caller must ensure that + // the HCURSOR remains valid by not invoking the DestroyCursor/DestroyIcon + // APIs on it. + void InitFromExternalCursor(HCURSOR handle); + +#elif defined(USE_X11) + // Return the stock GdkCursorType for this cursor, or GDK_CURSOR_IS_PIXMAP + // if it's a custom cursor. Return GDK_LAST_CURSOR to indicate that the cursor + // should be set to the system default. + // Returns an int so we don't need to include GDK headers in this header file. + int GetCursorType() const; + + // Return a new GdkCursor* for this cursor. Only valid if GetCursorType + // returns GDK_CURSOR_IS_PIXMAP. + GdkCursor* GetCustomCursor() const; +#elif defined(OS_MACOSX) + // Gets an NSCursor* for this cursor. + NSCursor* GetCursor() const; + + // Initialize this from the given Carbon ThemeCursor. + void InitFromThemeCursor(ThemeCursor cursor); + + // Initialize this from the given Carbon Cursor. + void InitFromCursor(const Cursor* cursor); + + // Initialize this from the given Cocoa NSCursor. + void InitFromNSCursor(NSCursor* cursor); +#endif + + private: + // Copies the contents of the WebCursor instance passed in. + void Copy(const WebCursor& other); + + // Cleans up the WebCursor instance. + void Clear(); + + // Platform specific initialization goes here. + void InitPlatformData(); + + // Platform specific Serialization / De-serialization + bool SerializePlatformData(Pickle* pickle) const; + bool DeserializePlatformData(const Pickle* pickle, void** iter); + + // Returns true if the platform data in the current cursor object + // matches that of the cursor passed in. + bool IsPlatformDataEqual(const WebCursor& other) const ; + + // Copies platform specific data from the WebCursor instance passed in. + void CopyPlatformData(const WebCursor& other); + + // Platform specific cleanup. + void CleanupPlatformData(); + + void SetCustomData(const WebKit::WebImage& image); + void ImageFromCustomData(WebKit::WebImage* image) const; + + // WebCore::PlatformCursor type. + int type_; + + gfx::Point hotspot_; + + // Custom cursor data, as 32-bit RGBA. + // Platform-inspecific because it can be serialized. + gfx::Size custom_size_; + std::vector<char> custom_data_; + +#if defined(OS_WIN) + // An externally generated HCURSOR. We assume that it remains valid, i.e we + // don't attempt to copy the HCURSOR. + HCURSOR external_cursor_; + // A custom cursor created from custom bitmap data by Webkit. + HCURSOR custom_cursor_; +#endif // OS_WIN +}; + +#endif // WEBKIT_GLUE_WEBCURSOR_H_ diff --git a/webkit/glue/webcursor_gtk.cc b/webkit/glue/webcursor_gtk.cc new file mode 100644 index 0000000..70c0f46 --- /dev/null +++ b/webkit/glue/webcursor_gtk.cc @@ -0,0 +1,218 @@ +// Copyright (c) 2010 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 "webkit/glue/webcursor.h" + +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" + +using WebKit::WebCursorInfo; + +namespace { + +// webcursor_gtk_data.h is taken directly from WebKit's CursorGtk.h. +#include "webkit/glue/webcursor_gtk_data.h" + +// This helper function is taken directly from WebKit's CursorGtk.cpp. +// It attempts to create a custom cursor from the data inlined in +// webcursor_gtk_data.h. +GdkCursor* GetInlineCustomCursor(CustomCursorType type) { + const CustomCursor& custom = CustomCursors[type]; + GdkCursor* cursor = gdk_cursor_new_from_name(gdk_display_get_default(), + custom.name); + if (!cursor) { + const GdkColor fg = { 0, 0, 0, 0 }; + const GdkColor bg = { 65535, 65535, 65535, 65535 }; + GdkPixmap* source = gdk_bitmap_create_from_data(NULL, custom.bits, + 32, 32); + GdkPixmap* mask = gdk_bitmap_create_from_data(NULL, custom.mask_bits, + 32, 32); + cursor = gdk_cursor_new_from_pixmap(source, mask, &fg, &bg, + custom.hot_x, custom.hot_y); + g_object_unref(source); + g_object_unref(mask); + } + return cursor; +} + +// For GTK 2.16 and beyond, GDK_BLANK_CURSOR is available. Before, we have to +// use a custom cursor. +#if !GTK_CHECK_VERSION(2, 16, 0) +// Get/create a custom cursor which is invisible. +GdkCursor* GetInvisibleCustomCursor() { + const char bits[] = { 0 }; + const GdkColor color = { 0, 0, 0, 0 }; + GdkPixmap* bitmap = gdk_bitmap_create_from_data(NULL, bits, 1, 1); + GdkCursor* cursor = + gdk_cursor_new_from_pixmap(bitmap, bitmap, &color, &color, 0, 0); + g_object_unref(bitmap); + return cursor; +} +#endif + +} // end anonymous namespace + +int WebCursor::GetCursorType() const { + // http://library.gnome.org/devel/gdk/2.12/gdk-Cursors.html has images + // of the default X theme, but beware that the user's cursor theme can + // change everything. + switch (type_) { + case WebCursorInfo::TypePointer: + return GDK_LAST_CURSOR; + case WebCursorInfo::TypeCross: + return GDK_CROSS; + case WebCursorInfo::TypeHand: + return GDK_HAND2; + case WebCursorInfo::TypeIBeam: + return GDK_XTERM; + case WebCursorInfo::TypeWait: + return GDK_WATCH; + case WebCursorInfo::TypeHelp: + return GDK_QUESTION_ARROW; + case WebCursorInfo::TypeEastResize: + return GDK_RIGHT_SIDE; + case WebCursorInfo::TypeNorthResize: + return GDK_TOP_SIDE; + case WebCursorInfo::TypeNorthEastResize: + return GDK_TOP_RIGHT_CORNER; + case WebCursorInfo::TypeNorthWestResize: + return GDK_TOP_LEFT_CORNER; + case WebCursorInfo::TypeSouthResize: + return GDK_BOTTOM_SIDE; + case WebCursorInfo::TypeSouthEastResize: + return GDK_BOTTOM_RIGHT_CORNER; + case WebCursorInfo::TypeSouthWestResize: + return GDK_BOTTOM_LEFT_CORNER; + case WebCursorInfo::TypeWestResize: + return GDK_LEFT_SIDE; + case WebCursorInfo::TypeNorthSouthResize: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeEastWestResize: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeNorthEastSouthWestResize: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeNorthWestSouthEastResize: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeColumnResize: + return GDK_SB_H_DOUBLE_ARROW; // TODO(evanm): is this correct? + case WebCursorInfo::TypeRowResize: + return GDK_SB_V_DOUBLE_ARROW; // TODO(evanm): is this correct? + case WebCursorInfo::TypeMiddlePanning: + return GDK_FLEUR; + case WebCursorInfo::TypeEastPanning: + return GDK_SB_RIGHT_ARROW; + case WebCursorInfo::TypeNorthPanning: + return GDK_SB_UP_ARROW; + case WebCursorInfo::TypeNorthEastPanning: + return GDK_TOP_RIGHT_CORNER; + case WebCursorInfo::TypeNorthWestPanning: + return GDK_TOP_LEFT_CORNER; + case WebCursorInfo::TypeSouthPanning: + return GDK_SB_DOWN_ARROW; + case WebCursorInfo::TypeSouthEastPanning: + return GDK_BOTTOM_RIGHT_CORNER; + case WebCursorInfo::TypeSouthWestPanning: + return GDK_BOTTOM_LEFT_CORNER; + case WebCursorInfo::TypeWestPanning: + return GDK_SB_LEFT_ARROW; + case WebCursorInfo::TypeMove: + return GDK_FLEUR; + case WebCursorInfo::TypeVerticalText: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeCell: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeContextMenu: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeAlias: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeProgress: + return GDK_WATCH; + case WebCursorInfo::TypeNoDrop: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeCopy: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeNone: +// See comment above |GetInvisibleCustomCursor()|. +#if !GTK_CHECK_VERSION(2, 16, 0) + return GDK_CURSOR_IS_PIXMAP; +#else + return GDK_BLANK_CURSOR; +#endif + case WebCursorInfo::TypeNotAllowed: + NOTIMPLEMENTED(); return GDK_LAST_CURSOR; + case WebCursorInfo::TypeZoomIn: + case WebCursorInfo::TypeZoomOut: + case WebCursorInfo::TypeCustom: + return GDK_CURSOR_IS_PIXMAP; + } + NOTREACHED(); + return GDK_LAST_CURSOR; +} + +GdkCursor* WebCursor::GetCustomCursor() const { + switch (type_) { +// See comment above |GetInvisibleCustomCursor()|. +#if !GTK_CHECK_VERSION(2, 16, 0) + case WebCursorInfo::TypeNone: + return GetInvisibleCustomCursor(); +#endif + case WebCursorInfo::TypeZoomIn: + return GetInlineCustomCursor(CustomCursorZoomIn); + case WebCursorInfo::TypeZoomOut: + return GetInlineCustomCursor(CustomCursorZoomOut); + } + + if (type_ != WebCursorInfo::TypeCustom) { + NOTREACHED(); + return NULL; + } + + const guchar* data = reinterpret_cast<const guchar*>(&custom_data_[0]); + GdkPixbuf* pixbuf = + gdk_pixbuf_new_from_data(data, + GDK_COLORSPACE_RGB, + TRUE, // has_alpha + 8, // bits_per_sample + custom_size_.width(), // width + custom_size_.height(), // height + custom_size_.width() * 4, // row stride + NULL, // data destroy function + NULL); // data destroy function extra data + + GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), + pixbuf, + hotspot_.x(), + hotspot_.y()); + + gdk_pixbuf_unref(pixbuf); + + return cursor; +} + +void WebCursor::InitPlatformData() { + return; +} + +bool WebCursor::SerializePlatformData(Pickle* pickle) const { + return true; +} + +bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) { + return true; +} + +bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { + return true; +} + +void WebCursor::CleanupPlatformData() { + return; +} + +void WebCursor::CopyPlatformData(const WebCursor& other) { + return; +} diff --git a/webkit/glue/webcursor_gtk_data.h b/webkit/glue/webcursor_gtk_data.h new file mode 100644 index 0000000..8bffcb7 --- /dev/null +++ b/webkit/glue/webcursor_gtk_data.h @@ -0,0 +1,253 @@ +// This file is a cut'n'paste from WebKit/WebCore/platform/gtk/CursorGtk.h, +// with slight modifications to fit within Chrome. Its original +// copyright is below. + +// This file intentionally doesn't have a header guard, since it is just +// data to be used within a C++ file. + +/* + * Copyright (C) 2001 Tim Copperfield <timecop@network.email.ne.jp> + * Copyright (C) 2007 Christian Dywan <christian@twotoasts.de> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +/* + These cursors are copied from Mozilla code: + http://lxr.mozilla.org/mozilla1.8/source/widget/src/gtk2/nsGtkCursors.h +*/ + +/* MOZ_CURSOR_VERTICAL_TEXT */ +static const char moz_vertical_text_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, + 0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, + 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_vertical_text_mask_bits[] = { + 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_CONTEXT_MENU */ +static const char moz_menu_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfd, 0x00, 0x00, + 0xfc, 0xff, 0x00, 0x00, 0x7c, 0x84, 0x00, 0x00, 0x6c, 0xfc, 0x00, 0x00, + 0xc4, 0x84, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x85, 0x00, 0x00, + 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_menu_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00, + 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xfe, 0x01, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, + 0xc0, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_COPY */ +static const char moz_copy_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x03, 0x00, 0x00, 0x7c, 0x30, 0x00, 0x00, 0x6c, 0x30, 0x00, 0x00, + 0xc4, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, + 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_copy_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, + 0xfe, 0x37, 0x00, 0x00, 0xfe, 0x7b, 0x00, 0x00, 0xfe, 0xfc, 0x00, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x00, 0x00, + 0xc0, 0x7b, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ALIAS */ +static const char moz_alias_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x03, 0x00, 0x00, 0x7c, 0xf0, 0x00, 0x00, 0x6c, 0xe0, 0x00, 0x00, + 0xc4, 0xf0, 0x00, 0x00, 0xc0, 0xb0, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, + 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_alias_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00, + 0xfe, 0xf7, 0x00, 0x00, 0xfe, 0xfb, 0x01, 0x00, 0xfe, 0xf0, 0x01, 0x00, + 0xee, 0xf9, 0x01, 0x00, 0xe4, 0xf9, 0x01, 0x00, 0xc0, 0xbf, 0x00, 0x00, + 0xc0, 0x3f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ZOOM_IN */ +static const char moz_zoom_in_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x62, 0x04, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00, + 0xf9, 0x09, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_zoom_in_mask_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/* MOZ_CURSOR_ZOOM_OUT */ +static const char moz_zoom_out_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00, + 0xf9, 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, + 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_zoom_out_mask_bits[] = { + 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, + 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, + 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +enum CustomCursorType { + CustomCursorCopy = 0, + CustomCursorAlias, + CustomCursorContextMenu, + CustomCursorZoomIn, + CustomCursorZoomOut, + CustomCursorVerticalText +} ; + +typedef struct { + const char* name; + const char* bits; + const char* mask_bits; + int hot_x; + int hot_y; +} CustomCursor; + +// create custom pixmap cursor from cursors in nsGTKCursorData.h +static const CustomCursor CustomCursors[] = { + { "copy", moz_copy_bits, moz_copy_mask_bits, 2, 2 }, + { "alias", moz_alias_bits, moz_alias_mask_bits, 2, 2 }, + { "context-menu", moz_menu_bits, moz_menu_mask_bits, 2, 2 }, + { "zoom-in", moz_zoom_in_bits, moz_zoom_in_mask_bits, 6, 6 }, + { "zoom-out", moz_zoom_out_bits, moz_zoom_out_mask_bits, 6, 6 }, + { "vertical-text", moz_vertical_text_bits, moz_vertical_text_mask_bits, 8, 4 }, +}; + +// This cursor intentionally left out of above structs. It is only used by +// RenderWidgetHostViewGtk. For an explanation see +// http://vektor-sigma.livejournal.com/1137.html (where it is referred +// to as left_ptr_watch). + +/* MOZ_CURSOR_SPINNING */ +static const char moz_spinning_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, + 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, + 0xfc, 0x3b, 0x00, 0x00, 0x7c, 0x38, 0x00, 0x00, 0x6c, 0x54, 0x00, 0x00, + 0xc4, 0xdc, 0x00, 0x00, 0xc0, 0x44, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, + 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static const char moz_spinning_mask_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, + 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x3b, 0x00, 0x00, + 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0x00, 0x00, + 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00, + 0xc0, 0x7f, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/webkit/glue/webcursor_mac.mm b/webkit/glue/webcursor_mac.mm new file mode 100644 index 0000000..a6e7fc0 --- /dev/null +++ b/webkit/glue/webcursor_mac.mm @@ -0,0 +1,384 @@ +// Copyright (c) 2008 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 "webkit/glue/webcursor.h" + +#import <AppKit/AppKit.h> +#include <Carbon/Carbon.h> + +#include "base/logging.h" +#include "base/nsimage_cache_mac.h" +#include "base/scoped_cftyperef.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" + +using WebKit::WebCursorInfo; +using WebKit::WebImage; +using WebKit::WebSize; + +namespace { + +// TODO: This image fetch can (and probably should) be serviced by the resource +// resource bundle instead of going through nsimage_cache. +NSCursor* LoadCursor(const char* name, int x, int y) { + NSString* file_name = [NSString stringWithUTF8String:name]; + DCHECK(file_name); + NSImage* cursor_image = nsimage_cache::ImageNamed(file_name); + DCHECK(cursor_image); + return [[[NSCursor alloc] initWithImage:cursor_image + hotSpot:NSMakePoint(x, y)] autorelease]; +} + +CGImageRef CreateCGImageFromCustomData(const std::vector<char>& custom_data, + const gfx::Size& custom_size) { + scoped_cftyperef<CGColorSpaceRef> cg_color(CGColorSpaceCreateDeviceRGB()); + // This is safe since we're not going to draw into the context we're creating. + void* data = const_cast<char*>(&custom_data[0]); + // The settings here match SetCustomData() below; keep in sync. + scoped_cftyperef<CGContextRef> context( + CGBitmapContextCreate(data, + custom_size.width(), + custom_size.height(), + 8, + custom_size.width()*4, + cg_color.get(), + kCGImageAlphaPremultipliedLast | + kCGBitmapByteOrder32Big)); + return CGBitmapContextCreateImage(context.get()); +} + +NSCursor* CreateCustomCursor(const std::vector<char>& custom_data, + const gfx::Size& custom_size, + const gfx::Point& hotspot) { + // CG throws a cocoa exception if we try to create an empty image, which + // results in an infinite loop. This CHECK ensures that we crash instead. + CHECK(!custom_data.empty()); + + scoped_cftyperef<CGImageRef> cg_image( + CreateCGImageFromCustomData(custom_data, custom_size)); + + NSBitmapImageRep* ns_bitmap = + [[NSBitmapImageRep alloc] initWithCGImage:cg_image.get()]; + NSImage* cursor_image = [[NSImage alloc] init]; + DCHECK(cursor_image); + [cursor_image addRepresentation:ns_bitmap]; + [ns_bitmap release]; + + NSCursor* cursor = [[NSCursor alloc] initWithImage:cursor_image + hotSpot:NSMakePoint(hotspot.x(), + hotspot.y())]; + [cursor_image release]; + + return [cursor autorelease]; +} + +} // namespace + +// We're matching Safari's cursor choices; see platform/mac/CursorMac.mm +NSCursor* WebCursor::GetCursor() const { + switch (type_) { + case WebCursorInfo::TypePointer: + return [NSCursor arrowCursor]; + case WebCursorInfo::TypeCross: + return LoadCursor("crossHairCursor", 11, 11); + case WebCursorInfo::TypeHand: + return LoadCursor("linkCursor", 6, 1); + case WebCursorInfo::TypeIBeam: + return [NSCursor IBeamCursor]; + case WebCursorInfo::TypeWait: + return LoadCursor("waitCursor", 7, 7); + case WebCursorInfo::TypeHelp: + return LoadCursor("helpCursor", 8, 8); + case WebCursorInfo::TypeEastResize: + case WebCursorInfo::TypeEastPanning: + return LoadCursor("eastResizeCursor", 14, 7); + case WebCursorInfo::TypeNorthResize: + case WebCursorInfo::TypeNorthPanning: + return LoadCursor("northResizeCursor", 7, 1); + case WebCursorInfo::TypeNorthEastResize: + case WebCursorInfo::TypeNorthEastPanning: + return LoadCursor("northEastResizeCursor", 14, 1); + case WebCursorInfo::TypeNorthWestResize: + case WebCursorInfo::TypeNorthWestPanning: + return LoadCursor("northWestResizeCursor", 0, 0); + case WebCursorInfo::TypeSouthResize: + case WebCursorInfo::TypeSouthPanning: + return LoadCursor("southResizeCursor", 7, 14); + case WebCursorInfo::TypeSouthEastResize: + case WebCursorInfo::TypeSouthEastPanning: + return LoadCursor("southEastResizeCursor", 14, 14); + case WebCursorInfo::TypeSouthWestResize: + case WebCursorInfo::TypeSouthWestPanning: + return LoadCursor("southWestResizeCursor", 1, 14); + case WebCursorInfo::TypeWestResize: + case WebCursorInfo::TypeWestPanning: + return LoadCursor("westResizeCursor", 1, 7); + case WebCursorInfo::TypeNorthSouthResize: + return LoadCursor("northSouthResizeCursor", 7, 7); + case WebCursorInfo::TypeEastWestResize: + return LoadCursor("eastWestResizeCursor", 7, 7); + case WebCursorInfo::TypeNorthEastSouthWestResize: + return LoadCursor("northEastSouthWestResizeCursor", 7, 7); + case WebCursorInfo::TypeNorthWestSouthEastResize: + return LoadCursor("northWestSouthEastResizeCursor", 7, 7); + case WebCursorInfo::TypeColumnResize: + return [NSCursor resizeLeftRightCursor]; + case WebCursorInfo::TypeRowResize: + return [NSCursor resizeUpDownCursor]; + case WebCursorInfo::TypeMiddlePanning: + case WebCursorInfo::TypeMove: + return LoadCursor("moveCursor", 7, 7); + case WebCursorInfo::TypeVerticalText: + return LoadCursor("verticalTextCursor", 7, 7); + case WebCursorInfo::TypeCell: + return LoadCursor("cellCursor", 7, 7); + case WebCursorInfo::TypeContextMenu: + return LoadCursor("contextMenuCursor", 3, 2); + case WebCursorInfo::TypeAlias: + return LoadCursor("aliasCursor", 11, 3); + case WebCursorInfo::TypeProgress: + return LoadCursor("progressCursor", 3, 2); + case WebCursorInfo::TypeNoDrop: + return LoadCursor("noDropCursor", 3, 1); + case WebCursorInfo::TypeCopy: + return LoadCursor("copyCursor", 3, 2); + case WebCursorInfo::TypeNone: + return LoadCursor("noneCursor", 7, 7); + case WebCursorInfo::TypeNotAllowed: + return LoadCursor("notAllowedCursor", 11, 11); + case WebCursorInfo::TypeZoomIn: + return LoadCursor("zoomInCursor", 7, 7); + case WebCursorInfo::TypeZoomOut: + return LoadCursor("zoomOutCursor", 7, 7); + case WebCursorInfo::TypeCustom: + return CreateCustomCursor(custom_data_, custom_size_, hotspot_); + } + NOTREACHED(); + return nil; +} + +void WebCursor::InitFromThemeCursor(ThemeCursor cursor) { + WebKit::WebCursorInfo cursor_info; + + switch (cursor) { + case kThemeArrowCursor: + cursor_info.type = WebCursorInfo::TypePointer; + break; + case kThemeCopyArrowCursor: + cursor_info.type = WebCursorInfo::TypeCopy; + break; + case kThemeAliasArrowCursor: + cursor_info.type = WebCursorInfo::TypeAlias; + break; + case kThemeContextualMenuArrowCursor: + cursor_info.type = WebCursorInfo::TypeContextMenu; + break; + case kThemeIBeamCursor: + cursor_info.type = WebCursorInfo::TypeIBeam; + break; + case kThemeCrossCursor: + case kThemePlusCursor: + cursor_info.type = WebCursorInfo::TypeCross; + break; + case kThemeWatchCursor: + case kThemeSpinningCursor: + cursor_info.type = WebCursorInfo::TypeWait; + break; + case kThemeClosedHandCursor: + case kThemeOpenHandCursor: + case kThemePointingHandCursor: + case kThemeCountingUpHandCursor: + case kThemeCountingDownHandCursor: + case kThemeCountingUpAndDownHandCursor: + cursor_info.type = WebCursorInfo::TypeHand; + break; + case kThemeResizeLeftCursor: + cursor_info.type = WebCursorInfo::TypeWestResize; + break; + case kThemeResizeRightCursor: + cursor_info.type = WebCursorInfo::TypeEastResize; + break; + case kThemeResizeLeftRightCursor: + cursor_info.type = WebCursorInfo::TypeEastWestResize; + break; + case kThemeNotAllowedCursor: + cursor_info.type = WebCursorInfo::TypeNotAllowed; + break; + case kThemeResizeUpCursor: + cursor_info.type = WebCursorInfo::TypeNorthResize; + break; + case kThemeResizeDownCursor: + cursor_info.type = WebCursorInfo::TypeSouthResize; + break; + case kThemeResizeUpDownCursor: + cursor_info.type = WebCursorInfo::TypeNorthSouthResize; + break; + case kThemePoofCursor: // *shrug* + default: + cursor_info.type = WebCursorInfo::TypePointer; + break; + } + + InitFromCursorInfo(cursor_info); +} + +void WebCursor::InitFromCursor(const Cursor* cursor) { + // This conversion isn't perfect (in particular, the inversion effect of + // data==1, mask==0 won't work). Not planning on fixing it. + + gfx::Size custom_size(16, 16); + std::vector<char> raw_data; + for (int row = 0; row < 16; ++row) { + unsigned short data = cursor->data[row]; + unsigned short mask = cursor->mask[row]; + + // The Core Endian flipper callback for 'CURS' doesn't flip Bits16 as if it + // were a short (which it is), so we flip it here. + data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF); + mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF); + + for (int bit = 0; bit < 16; ++bit) { + if (data & 0x8000) { + raw_data.push_back(0x00); + raw_data.push_back(0x00); + raw_data.push_back(0x00); + } else { + raw_data.push_back(0xFF); + raw_data.push_back(0xFF); + raw_data.push_back(0xFF); + } + if (mask & 0x8000) + raw_data.push_back(0xFF); + else + raw_data.push_back(0x00); + data <<= 1; + mask <<= 1; + } + } + + scoped_cftyperef<CGImageRef> cg_image( + CreateCGImageFromCustomData(raw_data, custom_size)); + + WebKit::WebCursorInfo cursor_info; + cursor_info.type = WebCursorInfo::TypeCustom; + cursor_info.hotSpot = WebKit::WebPoint(cursor->hotSpot.h, cursor->hotSpot.v); + cursor_info.customImage = cg_image.get(); + + InitFromCursorInfo(cursor_info); +} + +void WebCursor::InitFromNSCursor(NSCursor* cursor) { + WebKit::WebCursorInfo cursor_info; + + if ([cursor isEqual:[NSCursor arrowCursor]]) { + cursor_info.type = WebCursorInfo::TypePointer; + } else if ([cursor isEqual:[NSCursor IBeamCursor]]) { + cursor_info.type = WebCursorInfo::TypeIBeam; + } else if ([cursor isEqual:[NSCursor crosshairCursor]]) { + cursor_info.type = WebCursorInfo::TypeCross; + } else if ([cursor isEqual:[NSCursor pointingHandCursor]]) { + cursor_info.type = WebCursorInfo::TypeHand; + } else if ([cursor isEqual:[NSCursor resizeLeftCursor]]) { + cursor_info.type = WebCursorInfo::TypeWestResize; + } else if ([cursor isEqual:[NSCursor resizeRightCursor]]) { + cursor_info.type = WebCursorInfo::TypeEastResize; + } else if ([cursor isEqual:[NSCursor resizeLeftRightCursor]]) { + cursor_info.type = WebCursorInfo::TypeEastWestResize; + } else if ([cursor isEqual:[NSCursor resizeUpCursor]]) { + cursor_info.type = WebCursorInfo::TypeNorthResize; + } else if ([cursor isEqual:[NSCursor resizeDownCursor]]) { + cursor_info.type = WebCursorInfo::TypeSouthResize; + } else if ([cursor isEqual:[NSCursor resizeUpDownCursor]]) { + cursor_info.type = WebCursorInfo::TypeNorthSouthResize; + } else { + // Also handles the [NSCursor closedHandCursor], [NSCursor openHandCursor], + // and [NSCursor disappearingItemCursor] cases. Quick-and-dirty image + // conversion; TODO(avi): do better. + CGImageRef cg_image = nil; + NSImage* image = [cursor image]; + for (id rep in [image representations]) { + if ([rep isKindOfClass:[NSBitmapImageRep class]]) { + cg_image = [rep CGImage]; + break; + } + } + + if (cg_image) { + cursor_info.type = WebCursorInfo::TypeCustom; + NSPoint hot_spot = [cursor hotSpot]; + cursor_info.hotSpot = WebKit::WebPoint(hot_spot.x, hot_spot.y); + cursor_info.customImage = cg_image; + } else { + cursor_info.type = WebCursorInfo::TypePointer; + } + } + + InitFromCursorInfo(cursor_info); +} + +void WebCursor::SetCustomData(const WebImage& image) { + if (image.isNull()) + return; + + scoped_cftyperef<CGColorSpaceRef> cg_color( + CGColorSpaceCreateDeviceRGB()); + + const WebSize& image_dimensions = image.size(); + int image_width = image_dimensions.width; + int image_height = image_dimensions.height; + + size_t size = image_height * image_width * 4; + custom_data_.resize(size); + custom_size_.set_width(image_width); + custom_size_.set_height(image_height); + + // These settings match up with the code in CreateCustomCursor() above; keep + // them in sync. + // TODO(avi): test to ensure that the flags here are correct for RGBA + scoped_cftyperef<CGContextRef> context( + CGBitmapContextCreate(&custom_data_[0], + image_width, + image_height, + 8, + image_width * 4, + cg_color.get(), + kCGImageAlphaPremultipliedLast | + kCGBitmapByteOrder32Big)); + CGRect rect = CGRectMake(0, 0, image_width, image_height); + CGContextDrawImage(context.get(), rect, image.getCGImageRef()); +} + +void WebCursor::ImageFromCustomData(WebImage* image) const { + if (custom_data_.empty()) + return; + + scoped_cftyperef<CGImageRef> cg_image( + CreateCGImageFromCustomData(custom_data_, custom_size_)); + *image = cg_image.get(); +} + +void WebCursor::InitPlatformData() { + return; +} + +bool WebCursor::SerializePlatformData(Pickle* pickle) const { + return true; +} + +bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) { + return true; +} + +bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { + return true; +} + +void WebCursor::CleanupPlatformData() { + return; +} + +void WebCursor::CopyPlatformData(const WebCursor& other) { + return; +} diff --git a/webkit/glue/webcursor_unittest.cc b/webkit/glue/webcursor_unittest.cc new file mode 100644 index 0000000..5c1ddfe --- /dev/null +++ b/webkit/glue/webcursor_unittest.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/pickle.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/webcursor.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +TEST(WebCursorTest, CursorSerialization) { + WebCursor custom_cursor; + // This is a valid custom cursor. + Pickle ok_custom_pickle; + // Type and hotspots. + ok_custom_pickle.WriteInt(0); + ok_custom_pickle.WriteInt(0); + ok_custom_pickle.WriteInt(0); + // X & Y + ok_custom_pickle.WriteInt(1); + ok_custom_pickle.WriteInt(1); + // Data len including enough data for a 1x1 image. + ok_custom_pickle.WriteInt(4); + ok_custom_pickle.WriteUInt32(0); + // Custom Windows message. + ok_custom_pickle.WriteUInt32(0); + void* iter = NULL; + EXPECT_TRUE(custom_cursor.Deserialize(&ok_custom_pickle, &iter)); + + // This custom cursor has not been send with enough data. + Pickle short_custom_pickle; + // Type and hotspots. + short_custom_pickle.WriteInt(0); + short_custom_pickle.WriteInt(0); + short_custom_pickle.WriteInt(0); + // X & Y + short_custom_pickle.WriteInt(1); + short_custom_pickle.WriteInt(1); + // Data len not including enough data for a 1x1 image. + short_custom_pickle.WriteInt(3); + short_custom_pickle.WriteUInt32(0); + // Custom Windows message. + ok_custom_pickle.WriteUInt32(0); + iter = NULL; + EXPECT_FALSE(custom_cursor.Deserialize(&short_custom_pickle, &iter)); + + // This custom cursor has enough data but is too big. + Pickle large_custom_pickle; + // Type and hotspots. + large_custom_pickle.WriteInt(0); + large_custom_pickle.WriteInt(0); + large_custom_pickle.WriteInt(0); + // X & Y + static const int kTooBigSize = 4096 + 1; + large_custom_pickle.WriteInt(kTooBigSize); + large_custom_pickle.WriteInt(1); + // Data len including enough data for a 4097x1 image. + large_custom_pickle.WriteInt(kTooBigSize * 4); + for (int i = 0; i < kTooBigSize; ++i) + large_custom_pickle.WriteUInt32(0); + // Custom Windows message. + ok_custom_pickle.WriteUInt32(0); + iter = NULL; + EXPECT_FALSE(custom_cursor.Deserialize(&large_custom_pickle, &iter)); + + // This custom cursor uses negative lengths. + Pickle neg_custom_pickle; + // Type and hotspots. + neg_custom_pickle.WriteInt(0); + neg_custom_pickle.WriteInt(0); + neg_custom_pickle.WriteInt(0); + // X & Y + neg_custom_pickle.WriteInt(-1); + neg_custom_pickle.WriteInt(-1); + // Data len including enough data for a 1x1 image. + neg_custom_pickle.WriteInt(4); + neg_custom_pickle.WriteUInt32(0); + // Custom Windows message. + neg_custom_pickle.WriteUInt32(0); + iter = NULL; + EXPECT_FALSE(custom_cursor.Deserialize(&neg_custom_pickle, &iter)); +} + diff --git a/webkit/glue/webcursor_win.cc b/webkit/glue/webcursor_win.cc new file mode 100644 index 0000000..4435e06 --- /dev/null +++ b/webkit/glue/webcursor_win.cc @@ -0,0 +1,236 @@ +// Copyright (c) 2010 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/logging.h" +#include "base/pickle.h" +#include "gfx/gdi_util.h" +#include "grit/webkit_resources.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCursorInfo.h" +#include "webkit/glue/webcursor.h" + +using WebKit::WebCursorInfo; + +static LPCWSTR ToCursorID(WebCursorInfo::Type type) { + switch (type) { + case WebCursorInfo::TypePointer: + return IDC_ARROW; + case WebCursorInfo::TypeCross: + return IDC_CROSS; + case WebCursorInfo::TypeHand: + return IDC_HAND; + case WebCursorInfo::TypeIBeam: + return IDC_IBEAM; + case WebCursorInfo::TypeWait: + return IDC_WAIT; + case WebCursorInfo::TypeHelp: + return IDC_HELP; + case WebCursorInfo::TypeEastResize: + return IDC_SIZEWE; + case WebCursorInfo::TypeNorthResize: + return IDC_SIZENS; + case WebCursorInfo::TypeNorthEastResize: + return IDC_SIZENESW; + case WebCursorInfo::TypeNorthWestResize: + return IDC_SIZENWSE; + case WebCursorInfo::TypeSouthResize: + return IDC_SIZENS; + case WebCursorInfo::TypeSouthEastResize: + return IDC_SIZENWSE; + case WebCursorInfo::TypeSouthWestResize: + return IDC_SIZENESW; + case WebCursorInfo::TypeWestResize: + return IDC_SIZEWE; + case WebCursorInfo::TypeNorthSouthResize: + return IDC_SIZENS; + case WebCursorInfo::TypeEastWestResize: + return IDC_SIZEWE; + case WebCursorInfo::TypeNorthEastSouthWestResize: + return IDC_SIZENESW; + case WebCursorInfo::TypeNorthWestSouthEastResize: + return IDC_SIZENWSE; + case WebCursorInfo::TypeColumnResize: + return MAKEINTRESOURCE(IDC_COLRESIZE); + case WebCursorInfo::TypeRowResize: + return MAKEINTRESOURCE(IDC_ROWRESIZE); + case WebCursorInfo::TypeMiddlePanning: + return MAKEINTRESOURCE(IDC_PAN_MIDDLE); + case WebCursorInfo::TypeEastPanning: + return MAKEINTRESOURCE(IDC_PAN_EAST); + case WebCursorInfo::TypeNorthPanning: + return MAKEINTRESOURCE(IDC_PAN_NORTH); + case WebCursorInfo::TypeNorthEastPanning: + return MAKEINTRESOURCE(IDC_PAN_NORTH_EAST); + case WebCursorInfo::TypeNorthWestPanning: + return MAKEINTRESOURCE(IDC_PAN_NORTH_WEST); + case WebCursorInfo::TypeSouthPanning: + return MAKEINTRESOURCE(IDC_PAN_SOUTH); + case WebCursorInfo::TypeSouthEastPanning: + return MAKEINTRESOURCE(IDC_PAN_SOUTH_EAST); + case WebCursorInfo::TypeSouthWestPanning: + return MAKEINTRESOURCE(IDC_PAN_SOUTH_WEST); + case WebCursorInfo::TypeWestPanning: + return MAKEINTRESOURCE(IDC_PAN_WEST); + case WebCursorInfo::TypeMove: + return IDC_SIZEALL; + case WebCursorInfo::TypeVerticalText: + return MAKEINTRESOURCE(IDC_VERTICALTEXT); + case WebCursorInfo::TypeCell: + return MAKEINTRESOURCE(IDC_CELL); + case WebCursorInfo::TypeContextMenu: + return MAKEINTRESOURCE(IDC_ARROW); + case WebCursorInfo::TypeAlias: + return MAKEINTRESOURCE(IDC_ALIAS); + case WebCursorInfo::TypeProgress: + return IDC_APPSTARTING; + case WebCursorInfo::TypeNoDrop: + return IDC_NO; + case WebCursorInfo::TypeCopy: + return MAKEINTRESOURCE(IDC_COPYCUR); + case WebCursorInfo::TypeNone: + return IDC_ARROW; + case WebCursorInfo::TypeNotAllowed: + return IDC_NO; + case WebCursorInfo::TypeZoomIn: + return MAKEINTRESOURCE(IDC_ZOOMIN); + case WebCursorInfo::TypeZoomOut: + return MAKEINTRESOURCE(IDC_ZOOMOUT); + } + NOTREACHED(); + return NULL; +} + +static bool IsSystemCursorID(LPCWSTR cursor_id) { + return cursor_id >= IDC_ARROW; // See WinUser.h +} + +static WebCursorInfo::Type ToCursorType(HCURSOR cursor) { + static struct { + HCURSOR cursor; + WebCursorInfo::Type type; + } kStandardCursors[] = { + { LoadCursor(NULL, IDC_ARROW), WebCursorInfo::TypePointer }, + { LoadCursor(NULL, IDC_CROSS), WebCursorInfo::TypeCross }, + { LoadCursor(NULL, IDC_HAND), WebCursorInfo::TypeHand }, + { LoadCursor(NULL, IDC_IBEAM), WebCursorInfo::TypeIBeam }, + { LoadCursor(NULL, IDC_WAIT), WebCursorInfo::TypeWait }, + { LoadCursor(NULL, IDC_HELP), WebCursorInfo::TypeHelp }, + { LoadCursor(NULL, IDC_SIZENESW), WebCursorInfo::TypeNorthEastResize }, + { LoadCursor(NULL, IDC_SIZENWSE), WebCursorInfo::TypeNorthWestResize }, + { LoadCursor(NULL, IDC_SIZENS), WebCursorInfo::TypeNorthSouthResize }, + { LoadCursor(NULL, IDC_SIZEWE), WebCursorInfo::TypeEastWestResize }, + { LoadCursor(NULL, IDC_SIZEALL), WebCursorInfo::TypeMove }, + { LoadCursor(NULL, IDC_APPSTARTING), WebCursorInfo::TypeProgress }, + { LoadCursor(NULL, IDC_NO), WebCursorInfo::TypeNotAllowed }, + }; + for (int i = 0; i < arraysize(kStandardCursors); i++) { + if (cursor == kStandardCursors[i].cursor) + return kStandardCursors[i].type; + } + return WebCursorInfo::TypeCustom; +} + +HCURSOR WebCursor::GetCursor(HINSTANCE module_handle){ + if (!IsCustom()) { + const wchar_t* cursor_id = + ToCursorID(static_cast<WebCursorInfo::Type>(type_)); + + if (IsSystemCursorID(cursor_id)) + module_handle = NULL; + + return LoadCursor(module_handle, cursor_id); + } + + if (custom_cursor_) { + DCHECK(external_cursor_ == NULL); + return custom_cursor_; + } + + if (external_cursor_) + return external_cursor_; + + BITMAPINFO cursor_bitmap_info = {0}; + gfx::CreateBitmapHeader( + custom_size_.width(), custom_size_.height(), + reinterpret_cast<BITMAPINFOHEADER*>(&cursor_bitmap_info)); + HDC dc = GetDC(0); + HDC workingDC = CreateCompatibleDC(dc); + HBITMAP bitmap_handle = CreateDIBSection( + dc, &cursor_bitmap_info, DIB_RGB_COLORS, 0, 0, 0); + if (!custom_data_.empty()) + SetDIBits( + 0, bitmap_handle, 0, custom_size_.height(), &custom_data_[0], + &cursor_bitmap_info, DIB_RGB_COLORS); + + HBITMAP old_bitmap = reinterpret_cast<HBITMAP>( + SelectObject(workingDC, bitmap_handle)); + SetBkMode(workingDC, TRANSPARENT); + SelectObject(workingDC, old_bitmap); + + HBITMAP mask = CreateBitmap( + custom_size_.width(), custom_size_.height(), 1, 1, NULL); + ICONINFO ii = {0}; + ii.fIcon = FALSE; + ii.xHotspot = hotspot_.x(); + ii.yHotspot = hotspot_.y(); + ii.hbmMask = mask; + ii.hbmColor = bitmap_handle; + + custom_cursor_ = CreateIconIndirect(&ii); + + DeleteObject(mask); + DeleteObject(bitmap_handle); + DeleteDC(workingDC); + ReleaseDC(0, dc); + return custom_cursor_; +} + +void WebCursor::InitFromExternalCursor(HCURSOR cursor) { + WebCursorInfo::Type cursor_type = ToCursorType(cursor); + + InitFromCursorInfo(WebCursorInfo(cursor_type)); + + if (cursor_type == WebCursorInfo::TypeCustom) + external_cursor_ = cursor; +} + +void WebCursor::InitPlatformData() { + external_cursor_ = NULL; + custom_cursor_ = NULL; +} + +bool WebCursor::SerializePlatformData(Pickle* pickle) const { + // There are some issues with converting certain HCURSORS to bitmaps. The + // HCURSOR being a user object can be marshaled as is. + // HCURSORs are always 32 bits on Windows, even on 64 bit systems. + return pickle->WriteUInt32(reinterpret_cast<uint32>(external_cursor_)); +} + +bool WebCursor::DeserializePlatformData(const Pickle* pickle, void** iter) { + return pickle->ReadUInt32(iter, reinterpret_cast<uint32*>(&external_cursor_)); +} + +bool WebCursor::IsPlatformDataEqual(const WebCursor& other) const { + if (!IsCustom()) + return true; + + return (external_cursor_ == other.external_cursor_); +} + +void WebCursor::CopyPlatformData(const WebCursor& other) { + external_cursor_ = other.external_cursor_; + // The custom_cursor_ member will be initialized to a HCURSOR the next time + // the GetCursor member function is invoked on this WebCursor instance. The + // cursor is created using the data in the custom_data_ vector. + custom_cursor_ = NULL; +} + +void WebCursor::CleanupPlatformData() { + external_cursor_ = NULL; + + if (custom_cursor_) { + DestroyIcon(custom_cursor_); + custom_cursor_ = NULL; + } +} diff --git a/webkit/glue/webdropdata.cc b/webkit/glue/webdropdata.cc new file mode 100644 index 0000000..d46987a --- /dev/null +++ b/webkit/glue/webdropdata.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/webdropdata.h" + +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDragData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" + +using WebKit::WebData; +using WebKit::WebDragData; +using WebKit::WebString; +using WebKit::WebVector; + +WebDropData::WebDropData(const WebDragData& drag_data) + : identity(0), + url(drag_data.url()), + url_title(drag_data.urlTitle()), + download_metadata(drag_data.downloadMetadata()), + file_extension(drag_data.fileExtension()), + plain_text(drag_data.plainText()), + text_html(drag_data.htmlText()), + html_base_url(drag_data.htmlBaseURL()), + file_description_filename(drag_data.fileContentFileName()) { + if (drag_data.hasFileNames()) { + WebVector<WebString> fileNames; + drag_data.fileNames(fileNames); + for (size_t i = 0; i < fileNames.size(); ++i) + filenames.push_back(fileNames[i]); + } + WebData contents = drag_data.fileContent(); + if (!contents.isEmpty()) + file_contents.assign(contents.data(), contents.size()); +} + +WebDragData WebDropData::ToDragData() const { + WebDragData result; + result.initialize(); + result.setURL(url); + result.setURLTitle(url_title); + result.setFileExtension(file_extension); + result.setFileNames(filenames); + result.setPlainText(plain_text); + result.setHTMLText(text_html); + result.setHTMLBaseURL(html_base_url); + result.setFileContentFileName(file_description_filename); + result.setFileContent(WebData(file_contents.data(), file_contents.size())); + return result; +} diff --git a/webkit/glue/webdropdata.h b/webkit/glue/webdropdata.h new file mode 100644 index 0000000..8149d8e --- /dev/null +++ b/webkit/glue/webdropdata.h @@ -0,0 +1,73 @@ +// Copyright (c) 2006-2008 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. +// +// A struct for managing data being dropped on a webview. This represents a +// union of all the types of data that can be dropped in a platform neutral +// way. + +#ifndef WEBKIT_GLUE_WEBDROPDATA_H_ +#define WEBKIT_GLUE_WEBDROPDATA_H_ + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "googleurl/src/gurl.h" + +struct IDataObject; + +namespace WebKit { +class WebDragData; +} + +struct WebDropData { + // Construct with a given drag identity. Note: identity is an int32 because + // it is passed over the renderer NPAPI interface to gears. + explicit WebDropData(int32 drag_identity) : identity(drag_identity) {} + + // Construct from a WebDragData object. + explicit WebDropData(const WebKit::WebDragData&); + + // For default constructions, use drag |identity| 0. + WebDropData() : identity(0) {} + + int32 identity; + + // User is dragging a link into the webview. + GURL url; + string16 url_title; // The title associated with |url|. + + // User is dragging a link out-of the webview. + string16 download_metadata; + + // File extension for dragging images from a webview to the desktop. + string16 file_extension; + + // User is dropping one or more files on the webview. + std::vector<string16> filenames; + + // User is dragging plain text into the webview. + string16 plain_text; + + // User is dragging text/html into the webview (e.g., out of Firefox). + // |html_base_url| is the URL that the html fragment is taken from (used to + // resolve relative links). It's ok for |html_base_url| to be empty. + string16 text_html; + GURL html_base_url; + + // User is dragging data from the webview (e.g., an image). + string16 file_description_filename; + std::string file_contents; + + // Convert to a WebDragData object. + WebKit::WebDragData ToDragData() const; + + // Helper method for converting Window's specific IDataObject to a WebDropData + // object. TODO(tc): Move this to the browser side since it's Windows + // specific and no longer used in webkit. + static void PopulateWebDropData(IDataObject* data_object, + WebDropData* drop_data); +}; + +#endif // WEBKIT_GLUE_WEBDROPDATA_H_ diff --git a/webkit/glue/webdropdata_win.cc b/webkit/glue/webdropdata_win.cc new file mode 100644 index 0000000..bd76090 --- /dev/null +++ b/webkit/glue/webdropdata_win.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/webdropdata.h" + +#include <windows.h> +#include <shellapi.h> +#include <shlobj.h> + +#include "app/clipboard/clipboard_util_win.h" +#include "googleurl/src/gurl.h" + +// static +void WebDropData::PopulateWebDropData(IDataObject* data_object, + WebDropData* drop_data) { + std::wstring url_str; + if (ClipboardUtil::GetUrl(data_object, &url_str, &drop_data->url_title, + false)) { + GURL test_url(url_str); + if (test_url.is_valid()) + drop_data->url = test_url; + } + ClipboardUtil::GetFilenames(data_object, &drop_data->filenames); + ClipboardUtil::GetPlainText(data_object, &drop_data->plain_text); + std::string base_url; + ClipboardUtil::GetHtml(data_object, &drop_data->text_html, &base_url); + if (!base_url.empty()) { + drop_data->html_base_url = GURL(base_url); + } + ClipboardUtil::GetFileContents(data_object, + &drop_data->file_description_filename, &drop_data->file_contents); +} diff --git a/webkit/glue/webfilesystem_impl.cc b/webkit/glue/webfilesystem_impl.cc new file mode 100644 index 0000000..585287b --- /dev/null +++ b/webkit/glue/webfilesystem_impl.cc @@ -0,0 +1,154 @@ +// Copyright (c) 2010 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 "webkit/glue/webfilesystem_impl.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "net/base/file_stream.h" +#include "net/base/net_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebString; + +namespace webkit_glue { + +WebFileSystemImpl::WebFileSystemImpl() + : sandbox_enabled_(true) { +} + +bool WebFileSystemImpl::fileExists(const WebString& path) { + FilePath::StringType file_path = WebStringToFilePathString(path); + return file_util::PathExists(FilePath(file_path)); +} + +bool WebFileSystemImpl::deleteFile(const WebString& path) { + NOTREACHED(); + return false; +} + +bool WebFileSystemImpl::deleteEmptyDirectory(const WebString& path) { + NOTREACHED(); + return false; +} + +bool WebFileSystemImpl::getFileSize(const WebString& path, long long& result) { + if (sandbox_enabled_) { + NOTREACHED(); + return false; + } + return file_util::GetFileSize(WebStringToFilePath(path), + reinterpret_cast<int64*>(&result)); +} + +bool WebFileSystemImpl::getFileModificationTime(const WebString& path, + double& result) { + if (sandbox_enabled_) { + NOTREACHED(); + return false; + } + file_util::FileInfo info; + if (!file_util::GetFileInfo(WebStringToFilePath(path), &info)) + return false; + result = info.last_modified.ToDoubleT(); + return true; +} + +WebString WebFileSystemImpl::directoryName(const WebString& path) { + FilePath file_path(WebStringToFilePathString(path)); + return FilePathToWebString(file_path.DirName()); +} + +WebString WebFileSystemImpl::pathByAppendingComponent( + const WebString& webkit_path, + const WebString& webkit_component) { + FilePath path(WebStringToFilePathString(webkit_path)); + FilePath component(WebStringToFilePathString(webkit_component)); + FilePath combined_path = path.Append(component); + return FilePathStringToWebString(combined_path.value()); +} + +bool WebFileSystemImpl::makeAllDirectories(const WebString& path) { + DCHECK(!sandbox_enabled_); + FilePath::StringType file_path = WebStringToFilePathString(path); + return file_util::CreateDirectory(FilePath(file_path)); +} + +WebString WebFileSystemImpl::getAbsolutePath(const WebString& path) { + FilePath file_path(WebStringToFilePathString(path)); + file_util::AbsolutePath(&file_path); + return FilePathStringToWebString(file_path.value()); +} + +bool WebFileSystemImpl::isDirectory(const WebString& path) { + FilePath file_path(WebStringToFilePathString(path)); + return file_util::DirectoryExists(file_path); +} + +WebKit::WebURL WebFileSystemImpl::filePathToURL(const WebString& path) { + return net::FilePathToFileURL(WebStringToFilePath(path)); +} + +base::PlatformFile WebFileSystemImpl::openFile(const WebString& path, + int mode) { + if (sandbox_enabled_) { + NOTREACHED(); + return base::kInvalidPlatformFileValue; + } + return base::CreatePlatformFile( + WebStringToFilePath(path), + (mode == 0) ? (base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ) + : (base::PLATFORM_FILE_CREATE_ALWAYS | + base::PLATFORM_FILE_WRITE), + NULL); +} + +void WebFileSystemImpl::closeFile(base::PlatformFile& handle) { + if (handle == base::kInvalidPlatformFileValue) + return; + if (base::ClosePlatformFile(handle)) + handle = base::kInvalidPlatformFileValue; +} + +long long WebFileSystemImpl::seekFile(base::PlatformFile handle, + long long offset, + int origin) { + if (handle == base::kInvalidPlatformFileValue) + return -1; + net::FileStream file_stream(handle, 0); + return file_stream.Seek(static_cast<net::Whence>(origin), offset); +} + +bool WebFileSystemImpl::truncateFile(base::PlatformFile handle, + long long offset) { + if (handle == base::kInvalidPlatformFileValue || offset < 0) + return false; + net::FileStream file_stream(handle, base::PLATFORM_FILE_WRITE); + return file_stream.Truncate(offset) >= 0; +} + +int WebFileSystemImpl::readFromFile(base::PlatformFile handle, + char* data, + int length) { + if (handle == base::kInvalidPlatformFileValue || !data || length <= 0) + return -1; + std::string buffer; + buffer.resize(length); + net::FileStream file_stream(handle, base::PLATFORM_FILE_READ); + return file_stream.Read(data, length, NULL); +} + +int WebFileSystemImpl::writeToFile(base::PlatformFile handle, + const char* data, + int length) { + if (handle == base::kInvalidPlatformFileValue || !data || length <= 0) + return -1; + net::FileStream file_stream(handle, base::PLATFORM_FILE_WRITE); + return file_stream.Write(data, length, NULL); +} + +} // namespace webkit_glue diff --git a/webkit/glue/webfilesystem_impl.h b/webkit/glue/webfilesystem_impl.h new file mode 100644 index 0000000..875a13b --- /dev/null +++ b/webkit/glue/webfilesystem_impl.h @@ -0,0 +1,54 @@ +// Copyright (c) 2010 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 WEBFILESYSTEM_IMPL_H_ +#define WEBFILESYSTEM_IMPL_H_ + +#include "base/platform_file.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFileSystem.h" + +namespace webkit_glue { + +class WebFileSystemImpl : public WebKit::WebFileSystem { + public: + WebFileSystemImpl(); + virtual ~WebFileSystemImpl() { } + + // WebFileSystem methods: + virtual bool fileExists(const WebKit::WebString& path); + virtual bool deleteFile(const WebKit::WebString& path); + virtual bool deleteEmptyDirectory(const WebKit::WebString& path); + virtual bool getFileSize(const WebKit::WebString& path, long long& result); + virtual bool getFileModificationTime( + const WebKit::WebString& path, + double& result); + virtual WebKit::WebString directoryName(const WebKit::WebString& path); + virtual WebKit::WebString pathByAppendingComponent( + const WebKit::WebString& path, const WebKit::WebString& component); + virtual bool makeAllDirectories(const WebKit::WebString& path); + virtual WebKit::WebString getAbsolutePath(const WebKit::WebString& path); + virtual bool isDirectory(const WebKit::WebString& path); + virtual WebKit::WebURL filePathToURL(const WebKit::WebString& path); + virtual base::PlatformFile openFile(const WebKit::WebString& path, int mode); + virtual void closeFile(base::PlatformFile& handle); + virtual long long seekFile(base::PlatformFile handle, + long long offset, + int origin); + virtual bool truncateFile(base::PlatformFile handle, long long offset); + virtual int readFromFile(base::PlatformFile handle, char* data, int length); + virtual int writeToFile(base::PlatformFile handle, + const char* data, + int length); + + void set_sandbox_enabled(bool sandbox_enabled) { + sandbox_enabled_ = sandbox_enabled; + } + + protected: + bool sandbox_enabled_; +}; + +} // namespace webkit_glue + +#endif // WEBFILESYSTEM_IMPL_H_ diff --git a/webkit/glue/webframe_unittest.cc b/webkit/glue/webframe_unittest.cc new file mode 100644 index 0000000..6ffaf84 --- /dev/null +++ b/webkit/glue/webframe_unittest.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2008 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/string_util.h" +#include "base/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebFrame; +using WebKit::WebString; +using WebKit::WebView; + +class WebFrameTest : public TestShellTest { +}; + +TEST_F(WebFrameTest, GetContentAsPlainText) { + WebView* view = test_shell_->webView(); + WebFrame* frame = view->mainFrame(); + + // Generate a simple test case. + const char simple_source[] = "<div>Foo bar</div><div></div>baz"; + GURL test_url("http://foo/"); + frame->loadHTMLString(simple_source, test_url); + test_shell_->WaitTestFinished(); + + // Make sure it comes out OK. + const string16 expected(ASCIIToUTF16("Foo bar\nbaz")); + string16 text = frame->contentAsText(std::numeric_limits<size_t>::max()); + EXPECT_EQ(expected, text); + + // Try reading the same one with clipping of the text. + const int len = 5; + text = frame->contentAsText(len); + EXPECT_EQ(expected.substr(0, len), text); + + // Now do a new test with a subframe. + const char outer_frame_source[] = "Hello<iframe></iframe> world"; + frame->loadHTMLString(outer_frame_source, test_url); + test_shell_->WaitTestFinished(); + + // Load something into the subframe. + WebFrame* subframe = frame->findChildByExpression( + WebString::fromUTF8("/html/body/iframe")); + ASSERT_TRUE(subframe); + subframe->loadHTMLString("sub<p>text", test_url); + test_shell_->WaitTestFinished(); + + text = frame->contentAsText(std::numeric_limits<size_t>::max()); + EXPECT_EQ("Hello world\n\nsub\ntext", UTF16ToUTF8(text)); + + // Get the frame text where the subframe separator falls on the boundary of + // what we'll take. There used to be a crash in this case. + text = frame->contentAsText(12); + EXPECT_EQ("Hello world", UTF16ToUTF8(text)); +} + +TEST_F(WebFrameTest, GetFullHtmlOfPage) { + WebView* view = test_shell_->webView(); + WebFrame* frame = view->mainFrame(); + + // Generate a simple test case. + const char simple_source[] = "<p>Hello</p><p>World</p>"; + GURL test_url("http://hello/"); + frame->loadHTMLString(simple_source, test_url); + test_shell_->WaitTestFinished(); + + string16 text = frame->contentAsText(std::numeric_limits<size_t>::max()); + EXPECT_EQ("Hello\n\nWorld", UTF16ToUTF8(text)); + + const std::string html = frame->contentAsMarkup().utf8(); + + // Load again with the output html. + frame->loadHTMLString(html, test_url); + test_shell_->WaitTestFinished(); + + EXPECT_EQ(html, UTF16ToUTF8(frame->contentAsMarkup())); + + text = frame->contentAsText(std::numeric_limits<size_t>::max()); + EXPECT_EQ("Hello\n\nWorld", UTF16ToUTF8(text)); + + // Test selection check + EXPECT_FALSE(frame->hasSelection()); + frame->executeCommand(WebString::fromUTF8("SelectAll")); + EXPECT_TRUE(frame->hasSelection()); + frame->executeCommand(WebString::fromUTF8("Unselect")); + EXPECT_FALSE(frame->hasSelection()); + WebString selection_html = frame->selectionAsMarkup(); + EXPECT_TRUE(selection_html.isEmpty()); +} diff --git a/webkit/glue/webkit_glue.cc b/webkit/glue/webkit_glue.cc new file mode 100644 index 0000000..e6b0f75 --- /dev/null +++ b/webkit/glue/webkit_glue.cc @@ -0,0 +1,509 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/webkit_glue.h" + +#if defined(OS_WIN) +#include <objidl.h> +#include <mlang.h> +#elif defined(OS_POSIX) && !defined(OS_MACOSX) +#include <sys/utsname.h> +#endif + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/singleton.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "net/base/escape.h" +#include "skia/ext/platform_canvas.h" +#if defined(OS_MACOSX) +#include "skia/ext/skia_utils_mac.h" +#endif +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebGlyphCache.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHistoryItem.h" +#include "third_party/WebKit/WebKit/chromium/public/WebImage.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#if defined(OS_WIN) +#include "third_party/WebKit/WebKit/chromium/public/win/WebInputEventFactory.h" +#endif +#include "webkit/glue/glue_serialize.h" +#include "v8/include/v8.h" + +#include "webkit_version.h" // Generated + +using WebKit::WebCanvas; +using WebKit::WebData; +using WebKit::WebElement; +using WebKit::WebFrame; +using WebKit::WebGlyphCache; +using WebKit::WebHistoryItem; +using WebKit::WebImage; +using WebKit::WebSize; +using WebKit::WebString; +using WebKit::WebVector; +using WebKit::WebView; + +static const char kLayoutTestsPattern[] = "/LayoutTests/"; +static const std::string::size_type kLayoutTestsPatternSize = + arraysize(kLayoutTestsPattern) - 1; +static const char kFileUrlPattern[] = "file:/"; +static const char kDataUrlPattern[] = "data:"; +static const std::string::size_type kDataUrlPatternSize = + arraysize(kDataUrlPattern) - 1; +static const char kFileTestPrefix[] = "(file test):"; + +//------------------------------------------------------------------------------ +// webkit_glue impl: + +namespace webkit_glue { + +// Global variable used by the plugin quirk "die after unload". +bool g_forcefully_terminate_plugin_process = false; + +void SetJavaScriptFlags(const std::wstring& str) { +#if WEBKIT_USING_V8 + std::string utf8_str = WideToUTF8(str); + v8::V8::SetFlagsFromString( + utf8_str.data(), static_cast<int>(utf8_str.size())); +#endif +} + +void EnableWebCoreNotImplementedLogging() { + WebKit::enableLogChannel("NotYetImplemented"); +} + +std::wstring DumpDocumentText(WebFrame* web_frame) { + // We use the document element's text instead of the body text here because + // not all documents have a body, such as XML documents. + WebElement document_element = web_frame->document().documentElement(); + if (document_element.isNull()) + return std::wstring(); + + return UTF16ToWideHack(document_element.innerText()); +} + +std::wstring DumpFramesAsText(WebFrame* web_frame, bool recursive) { + std::wstring result; + + // Add header for all but the main frame. Skip empty frames. + if (web_frame->parent() && + !web_frame->document().documentElement().isNull()) { + result.append(L"\n--------\nFrame: '"); + result.append(UTF16ToWideHack(web_frame->name())); + result.append(L"'\n--------\n"); + } + + result.append(DumpDocumentText(web_frame)); + result.append(L"\n"); + + if (recursive) { + WebFrame* child = web_frame->firstChild(); + for (; child; child = child->nextSibling()) + result.append(DumpFramesAsText(child, recursive)); + } + + return result; +} + +std::wstring DumpRenderer(WebFrame* web_frame) { + return UTF16ToWideHack(web_frame->renderTreeAsText()); +} + +bool CounterValueForElementById(WebFrame* web_frame, const std::string& id, + std::wstring* counter_value) { + WebString result = + web_frame->counterValueForElementById(WebString::fromUTF8(id)); + if (result.isNull()) + return false; + + *counter_value = UTF16ToWideHack(result); + return true; +} + +int PageNumberForElementById(WebFrame* web_frame, + const std::string& id, + float page_width_in_pixels, + float page_height_in_pixels) { + return web_frame->pageNumberForElementById(WebString::fromUTF8(id), + page_width_in_pixels, + page_height_in_pixels); +} + +int NumberOfPages(WebFrame* web_frame, + float page_width_in_pixels, + float page_height_in_pixels) { + WebSize size(static_cast<int>(page_width_in_pixels), + static_cast<int>(page_height_in_pixels)); + int number_of_pages = web_frame->printBegin(size); + web_frame->printEnd(); + return number_of_pages; +} + +std::wstring DumpFrameScrollPosition(WebFrame* web_frame, bool recursive) { + gfx::Size offset = web_frame->scrollOffset(); + std::wstring result; + + if (offset.width() > 0 || offset.height() > 0) { + if (web_frame->parent()) { + StringAppendF(&result, L"frame '%ls' ", UTF16ToWide( + web_frame->name()).c_str()); + } + StringAppendF(&result, L"scrolled to %d,%d\n", + offset.width(), offset.height()); + } + + if (recursive) { + WebFrame* child = web_frame->firstChild(); + for (; child; child = child->nextSibling()) + result.append(DumpFrameScrollPosition(child, recursive)); + } + + return result; +} + +// Returns True if item1 < item2. +static bool HistoryItemCompareLess(const WebHistoryItem& item1, + const WebHistoryItem& item2) { + string16 target1 = item1.target(); + string16 target2 = item2.target(); + std::transform(target1.begin(), target1.end(), target1.begin(), tolower); + std::transform(target2.begin(), target2.end(), target2.begin(), tolower); + return target1 < target2; +} + +// Writes out a HistoryItem into a string in a readable format. +static std::wstring DumpHistoryItem(const WebHistoryItem& item, + int indent, bool is_current) { + std::wstring result; + + if (is_current) { + result.append(L"curr->"); + result.append(indent - 6, L' '); // 6 == L"curr->".length() + } else { + result.append(indent, L' '); + } + + std::string url = item.urlString().utf8(); + size_t pos; + if (url.find(kFileUrlPattern) == 0 && + ((pos = url.find(kLayoutTestsPattern)) != std::string::npos)) { + // adjust file URLs to match upstream results. + url.replace(0, pos + kLayoutTestsPatternSize, kFileTestPrefix); + } else if (url.find(kDataUrlPattern) == 0) { + // URL-escape data URLs to match results upstream. + std::string path = EscapePath(url.substr(kDataUrlPatternSize)); + url.replace(kDataUrlPatternSize, url.length(), path); + } + + result.append(UTF8ToWide(url)); + if (!item.target().isEmpty()) + result.append(L" (in frame \"" + UTF16ToWide(item.target()) + L"\")"); + if (item.isTargetItem()) + result.append(L" **nav target**"); + result.append(L"\n"); + + const WebVector<WebHistoryItem>& children = item.children(); + if (!children.isEmpty()) { + // Must sort to eliminate arbitrary result ordering which defeats + // reproducible testing. + // TODO(darin): WebVector should probably just be a std::vector!! + std::vector<WebHistoryItem> sorted_children; + for (size_t i = 0; i < children.size(); ++i) + sorted_children.push_back(children[i]); + std::sort(sorted_children.begin(), sorted_children.end(), + HistoryItemCompareLess); + for (size_t i = 0; i < sorted_children.size(); i++) + result += DumpHistoryItem(sorted_children[i], indent+4, false); + } + + return result; +} + +std::wstring DumpHistoryState(const std::string& history_state, int indent, + bool is_current) { + return DumpHistoryItem(HistoryItemFromString(history_state), indent, + is_current); +} + +void ResetBeforeTestRun(WebView* view) { + WebFrame* web_frame = view->mainFrame(); + + // Reset the main frame name since tests always expect it to be empty. It + // is normally not reset between page loads (even in IE and FF). + if (web_frame) + web_frame->setName(WebString()); + +#if defined(OS_WIN) + // Reset the last click information so the clicks generated from previous + // test aren't inherited (otherwise can mistake single/double/triple clicks) + WebKit::WebInputEventFactory::resetLastClickState(); +#endif +} + +#ifndef NDEBUG +// The log macro was having problems due to collisions with WTF, so we just +// code here what that would have inlined. +void DumpLeakedObject(const char* file, int line, const char* object, int count) { + std::string msg = StringPrintf("%s LEAKED %d TIMES", object, count); + AppendToLog(file, line, msg.c_str()); +} +#endif + +void CheckForLeaks() { +#ifndef NDEBUG + int count = WebFrame::instanceCount(); + if (count) + DumpLeakedObject(__FILE__, __LINE__, "WebFrame", count); +#endif +} + +bool DecodeImage(const std::string& image_data, SkBitmap* image) { + WebData web_data(image_data.data(), image_data.length()); + WebImage web_image(WebImage::fromData(web_data, WebSize())); + if (web_image.isNull()) + return false; + +#if defined(OS_MACOSX) + *image = gfx::CGImageToSkBitmap(web_image.getCGImageRef()); +#else + *image = web_image.getSkBitmap(); +#endif + return true; +} + +// NOTE: This pair of conversion functions are here instead of in glue_util.cc +// since that file will eventually die. This pair of functions will need to +// remain as the concept of a file-path specific character encoding string type +// will most likely not make its way into WebKit. + +FilePath::StringType WebStringToFilePathString(const WebString& str) { +#if defined(OS_POSIX) + return base::SysWideToNativeMB(UTF16ToWideHack(str)); +#elif defined(OS_WIN) + return UTF16ToWideHack(str); +#endif +} + +WebString FilePathStringToWebString(const FilePath::StringType& str) { +#if defined(OS_POSIX) + return WideToUTF16Hack(base::SysNativeMBToWide(str)); +#elif defined(OS_WIN) + return WideToUTF16Hack(str); +#endif +} + +FilePath WebStringToFilePath(const WebString& str) { + return FilePath(WebStringToFilePathString(str)); +} + +WebString FilePathToWebString(const FilePath& file_path) { + return FilePathStringToWebString(file_path.value()); +} + +std::string GetWebKitVersion() { + return StringPrintf("%d.%d", WEBKIT_VERSION_MAJOR, WEBKIT_VERSION_MINOR); +} + +namespace { + +struct UserAgentState { + UserAgentState() + : user_agent_requested(false), + user_agent_is_overridden(false) { + } + + std::string user_agent; + + // The UA string when we're pretending to be Windows Chrome. + std::string mimic_windows_user_agent; + + bool user_agent_requested; + bool user_agent_is_overridden; +}; + +Singleton<UserAgentState> g_user_agent; + +std::string BuildOSCpuInfo() { + std::string os_cpu; + +#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS) + int32 os_major_version = 0; + int32 os_minor_version = 0; + int32 os_bugfix_version = 0; + base::SysInfo::OperatingSystemVersionNumbers(&os_major_version, + &os_minor_version, + &os_bugfix_version); +#endif +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Should work on any Posix system. + struct utsname unixinfo; + uname(&unixinfo); + + std::string cputype; + // special case for biarch systems + if (strcmp(unixinfo.machine, "x86_64") == 0 && + sizeof(void*) == sizeof(int32)) { + cputype.assign("i686 (x86_64)"); + } else { + cputype.assign(unixinfo.machine); + } +#endif + + StringAppendF( + &os_cpu, +#if defined(OS_WIN) + "Windows NT %d.%d", + os_major_version, + os_minor_version +#elif defined(OS_MACOSX) + "Intel Mac OS X %d_%d_%d", + os_major_version, + os_minor_version, + os_bugfix_version +#elif defined(OS_CHROMEOS) + "CrOS %s %d.%d.%d", + cputype.c_str(), // e.g. i686 + os_major_version, + os_minor_version, + os_bugfix_version +#else + "%s %s", + unixinfo.sysname, // e.g. Linux + cputype.c_str() // e.g. i686 +#endif + ); + + return os_cpu; +} + +// Construct the User-Agent header, filling in |result|. +// The other parameters are workarounds for broken websites: +// - If mimic_windows is true, produce a fake Windows Chrome string. +void BuildUserAgent(bool mimic_windows, std::string* result) { + const char kUserAgentPlatform[] = +#if defined(OS_WIN) + "Windows"; +#elif defined(OS_MACOSX) + "Macintosh"; +#elif defined(USE_X11) + "X11"; // strange, but that's what Firefox uses +#else + "?"; +#endif + + const char kUserAgentSecurity = 'U'; // "US" strength encryption + + // TODO(port): figure out correct locale + const char kUserAgentLocale[] = "en-US"; + + // Get the product name and version, and replace Safari's Version/X string + // with it. This is done to expose our product name in a manner that is + // maximally compatible with Safari, we hope!! + std::string product = GetProductVersion(); + + // Derived from Safari's UA string. + StringAppendF( + result, + "Mozilla/5.0 (%s; %c; %s; %s) AppleWebKit/%d.%d" + " (KHTML, like Gecko) %s Safari/%d.%d", + mimic_windows ? "Windows" : kUserAgentPlatform, + kUserAgentSecurity, + ((mimic_windows ? "Windows " : "") + BuildOSCpuInfo()).c_str(), + kUserAgentLocale, + WEBKIT_VERSION_MAJOR, + WEBKIT_VERSION_MINOR, + product.c_str(), + WEBKIT_VERSION_MAJOR, + WEBKIT_VERSION_MINOR + ); +} + +void SetUserAgentToDefault() { + BuildUserAgent(false, &g_user_agent->user_agent); +} + +} // namespace + +void SetUserAgent(const std::string& new_user_agent) { + // If you combine this with the previous line, the function only works the + // first time. + DCHECK(!g_user_agent->user_agent_requested) << + "Setting the user agent after someone has " + "already requested it can result in unexpected behavior."; + g_user_agent->user_agent_is_overridden = true; + g_user_agent->user_agent = new_user_agent; +} + +const std::string& GetUserAgent(const GURL& url) { + if (g_user_agent->user_agent.empty()) + SetUserAgentToDefault(); + g_user_agent->user_agent_requested = true; + if (!g_user_agent->user_agent_is_overridden) { + // Workarounds for sites that use misguided UA sniffing. +#if defined(OS_POSIX) && !defined(OS_MACOSX) + if (MatchPatternASCII(url.host(), "*.mail.yahoo.com")) { + // mail.yahoo.com is ok with Windows Chrome but not Linux Chrome. + // http://bugs.chromium.org/11136 + // TODO(evanm): remove this if Yahoo fixes their sniffing. + if (g_user_agent->mimic_windows_user_agent.empty()) + BuildUserAgent(true, &g_user_agent->mimic_windows_user_agent); + return g_user_agent->mimic_windows_user_agent; + } +#endif + } + return g_user_agent->user_agent; +} + +void SetForcefullyTerminatePluginProcess(bool value) { + if (IsPluginRunningInRendererProcess()) { + // Ignore this quirk when the plugins are not running in their own process. + return; + } + + g_forcefully_terminate_plugin_process = value; +} + +bool ShouldForcefullyTerminatePluginProcess() { + return g_forcefully_terminate_plugin_process; +} + +WebCanvas* ToWebCanvas(skia::PlatformCanvas* canvas) { +#if WEBKIT_USING_SKIA + return canvas; +#elif WEBKIT_USING_CG + return canvas->getTopPlatformDevice().GetBitmapContext(); +#else + NOTIMPLEMENTED(); + return NULL; +#endif +} + +int GetGlyphPageCount() { + return WebGlyphCache::pageCount(); +} + +bool g_enable_media_cache = false; + +bool IsMediaCacheEnabled() { + return g_enable_media_cache; +} + +void SetMediaCacheEnabled(bool enabled) { + g_enable_media_cache = enabled; +} + +} // namespace webkit_glue diff --git a/webkit/glue/webkit_glue.gypi b/webkit/glue/webkit_glue.gypi new file mode 100644 index 0000000..49320fc --- /dev/null +++ b/webkit/glue/webkit_glue.gypi @@ -0,0 +1,420 @@ +# Copyright (c) 2010 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. + +{ + 'variables': { + 'conditions': [ + ['inside_chromium_build==0', { + 'webkit_src_dir': '../../../..', + },{ + 'webkit_src_dir': '../../third_party/WebKit', + }], + ], + + 'grit_info_cmd': ['python', '<(DEPTH)/tools/grit/grit_info.py'], + 'grit_cmd': ['python', '<(DEPTH)/tools/grit/grit.py'], + }, + 'targets': [ + { + 'target_name': 'webkit_resources', + 'type': 'none', + 'msvs_guid': '0B469837-3D46-484A-AFB3-C5A6C68730B9', + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/webkit', + }, + 'actions': [ + { + 'action_name': 'webkit_resources', + 'variables': { + 'input_path': './webkit_resources.grd', + }, + 'inputs': [ + '<!@(<(grit_info_cmd) --inputs <(input_path))', + ], + 'outputs': [ + '<!@(<(grit_info_cmd) --outputs \'<(grit_out_dir)\' <(input_path))', + ], + 'action': ['<@(grit_cmd)', + '-i', '<(input_path)', 'build', + '-o', '<(grit_out_dir)'], + 'message': 'Generating resources from <(input_path)', + }, + { + 'action_name': 'webkit_chromium_resources', + 'variables': { + 'input_path': '<(webkit_src_dir)/WebKit/chromium/WebKit.grd', + }, + 'inputs': [ + '<!@(<(grit_info_cmd) --inputs <(input_path))', + ], + 'outputs': [ + '<!@(<(grit_info_cmd) --outputs \'<(grit_out_dir)\' <(input_path))', + ], + 'action': ['<@(grit_cmd)', + '-i', '<(input_path)', 'build', + '-o', '<(grit_out_dir)'], + 'message': 'Generating resources from <(input_path)', + }, + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/webkit', + ], + }, + 'conditions': [ + ['OS=="win"', { + 'dependencies': ['<(DEPTH)/build/win/system.gyp:cygwin'], + }], + ], + }, + { + 'target_name': 'webkit_strings', + 'type': 'none', + 'msvs_guid': '60B43839-95E6-4526-A661-209F16335E0E', + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/webkit', + }, + 'actions': [ + { + 'action_name': 'webkit_strings', + 'variables': { + 'input_path': './webkit_strings.grd', + }, + 'inputs': [ + '<!@(<(grit_info_cmd) --inputs <(input_path))', + ], + 'outputs': [ + '<!@(<(grit_info_cmd) --outputs \'<(grit_out_dir)\' <(input_path))', + ], + 'action': ['<@(grit_cmd)', + '-i', '<(input_path)', 'build', + '-o', '<(grit_out_dir)'], + 'message': 'Generating resources from <(input_path)', + }, + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/webkit', + ], + }, + 'conditions': [ + ['OS=="win"', { + 'dependencies': ['<(DEPTH)/build/win/system.gyp:cygwin'], + }], + ], + }, + { + 'target_name': 'glue', + 'type': '<(library)', + 'msvs_guid': 'C66B126D-0ECE-4CA2-B6DC-FA780AFBBF09', + 'dependencies': [ + '<(DEPTH)/app/app.gyp:app_base', + '<(DEPTH)/base/base.gyp:base_i18n', + '<(DEPTH)/net/net.gyp:net', + '<(DEPTH)/printing/printing.gyp:printing', + '<(DEPTH)/skia/skia.gyp:skia', + '<(DEPTH)/third_party/icu/icu.gyp:icui18n', + '<(DEPTH)/third_party/icu/icu.gyp:icuuc', + '<(DEPTH)/third_party/npapi/npapi.gyp:npapi', + '<(DEPTH)/third_party/ppapi/ppapi.gyp:ppapi_c', + 'webkit_resources', + 'webkit_strings', + ], + 'actions': [ + { + 'action_name': 'webkit_version', + 'inputs': [ + '../build/webkit_version.py', + '<(webkit_src_dir)/WebCore/Configurations/Version.xcconfig', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/webkit_version.h', + ], + 'action': ['python', '<@(_inputs)', '<(INTERMEDIATE_DIR)'], + }, + ], + 'include_dirs': [ + '<(INTERMEDIATE_DIR)', + '<(SHARED_INTERMEDIATE_DIR)/webkit', + ], + 'sources': [ + # This list contains all .h, .cc, and .mm files in glue except for + # those in the test subdirectory and those with unittest in in their + # names. + 'devtools_message_data.cc', + 'devtools_message_data.h', + 'media/buffered_data_source.cc', + 'media/buffered_data_source.h', + 'media/media_resource_loader_bridge_factory.cc', + 'media/media_resource_loader_bridge_factory.h', + 'media/simple_data_source.cc', + 'media/simple_data_source.h', + 'media/video_renderer_impl.cc', + 'media/video_renderer_impl.h', + 'media/web_video_renderer.h', + 'plugins/carbon_plugin_window_tracker_mac.h', + 'plugins/carbon_plugin_window_tracker_mac.cc', + 'plugins/coregraphics_private_symbols_mac.h', + 'plugins/default_plugin_shared.h', + 'plugins/nphostapi.h', + 'plugins/gtk_plugin_container.h', + 'plugins/gtk_plugin_container.cc', + 'plugins/gtk_plugin_container_manager.h', + 'plugins/gtk_plugin_container_manager.cc', + 'plugins/npapi_extension_thunk.cc', + 'plugins/npapi_extension_thunk.h', + 'plugins/pepper_buffer.cc', + 'plugins/pepper_buffer.h', + 'plugins/pepper_device_context_2d.cc', + 'plugins/pepper_device_context_2d.h', + 'plugins/pepper_directory_reader.cc', + 'plugins/pepper_directory_reader.h', + 'plugins/pepper_event_conversion.cc', + 'plugins/pepper_event_conversion.h', + 'plugins/pepper_file_chooser.cc', + 'plugins/pepper_file_chooser.h', + 'plugins/pepper_file_io.cc', + 'plugins/pepper_file_io.h', + 'plugins/pepper_file_ref.cc', + 'plugins/pepper_file_ref.h', + 'plugins/pepper_file_system.cc', + 'plugins/pepper_file_system.h', + 'plugins/pepper_font.cc', + 'plugins/pepper_font.h', + 'plugins/pepper_image_data.cc', + 'plugins/pepper_image_data.h', + 'plugins/pepper_plugin_delegate.h', + 'plugins/pepper_plugin_instance.cc', + 'plugins/pepper_plugin_instance.h', + 'plugins/pepper_plugin_module.cc', + 'plugins/pepper_plugin_module.h', + 'plugins/pepper_private.cc', + 'plugins/pepper_private.h', + 'plugins/pepper_resource_tracker.cc', + 'plugins/pepper_resource_tracker.h', + 'plugins/pepper_resource.cc', + 'plugins/pepper_resource.h', + 'plugins/pepper_scrollbar.cc', + 'plugins/pepper_scrollbar.h', + 'plugins/pepper_url_loader.cc', + 'plugins/pepper_url_loader.h', + 'plugins/pepper_url_request_info.cc', + 'plugins/pepper_url_request_info.h', + 'plugins/pepper_url_response_info.cc', + 'plugins/pepper_url_response_info.h', + 'plugins/pepper_var.cc', + 'plugins/pepper_var.h', + 'plugins/pepper_webplugin_impl.cc', + 'plugins/pepper_webplugin_impl.h', + 'plugins/pepper_widget.cc', + 'plugins/pepper_widget.h', + 'plugins/plugin_constants_win.h', + 'plugins/plugin_host.cc', + 'plugins/plugin_host.h', + 'plugins/plugin_instance.cc', + 'plugins/plugin_instance.h', + 'plugins/plugin_instance_mac.mm', + 'plugins/plugin_lib.cc', + 'plugins/plugin_lib.h', + 'plugins/plugin_lib_mac.mm', + 'plugins/plugin_lib_posix.cc', + 'plugins/plugin_lib_win.cc', + 'plugins/plugin_list.cc', + 'plugins/plugin_list.h', + 'plugins/plugin_list_mac.mm', + 'plugins/plugin_list_posix.cc', + 'plugins/plugin_list_win.cc', + 'plugins/plugin_stream.cc', + 'plugins/plugin_stream.h', + 'plugins/plugin_stream_posix.cc', + 'plugins/plugin_stream_url.cc', + 'plugins/plugin_stream_url.h', + 'plugins/plugin_stream_win.cc', + 'plugins/plugin_string_stream.cc', + 'plugins/plugin_string_stream.h', + 'plugins/plugin_stubs.cc', + 'plugins/plugin_switches.cc', + 'plugins/plugin_switches.h', + 'plugins/plugin_web_event_converter_mac.h', + 'plugins/plugin_web_event_converter_mac.mm', + 'plugins/ppb_private.h', + 'plugins/quickdraw_drawing_manager_mac.h', + 'plugins/quickdraw_drawing_manager_mac.cc', + 'plugins/webview_plugin.cc', + 'plugins/webview_plugin.h', + 'plugins/webplugin.cc', + 'plugins/webplugin.h', + 'plugins/webplugin_2d_device_delegate.h', + 'plugins/webplugin_3d_device_delegate.h', + 'plugins/webplugin_delegate.h', + 'plugins/webplugin_delegate_impl.cc', + 'plugins/webplugin_delegate_impl.h', + 'plugins/webplugin_delegate_impl_gtk.cc', + 'plugins/webplugin_delegate_impl_mac.mm', + 'plugins/webplugin_delegate_impl_win.cc', + 'plugins/webplugin_impl.cc', + 'plugins/webplugin_impl.h', + 'plugins/webplugininfo.h', + 'alt_error_page_resource_fetcher.cc', + 'alt_error_page_resource_fetcher.h', + 'context_menu.h', + 'cpp_binding_example.cc', + 'cpp_binding_example.h', + 'cpp_bound_class.cc', + 'cpp_bound_class.h', + 'cpp_variant.cc', + 'cpp_variant.h', + 'dom_operations.cc', + 'dom_operations.h', + 'form_data.h', + 'form_field.cc', + 'form_field.h', + 'ftp_directory_listing_response_delegate.cc', + 'ftp_directory_listing_response_delegate.h', + 'glue_serialize.cc', + 'glue_serialize.h', + 'image_decoder.cc', + 'image_decoder.h', + 'image_resource_fetcher.cc', + 'image_resource_fetcher.h', + 'multipart_response_delegate.cc', + 'multipart_response_delegate.h', + 'npruntime_util.cc', + 'npruntime_util.h', + 'password_form.h', + 'password_form_dom_manager.cc', + 'password_form_dom_manager.h', + 'resource_fetcher.cc', + 'resource_fetcher.h', + 'resource_loader_bridge.cc', + 'resource_loader_bridge.h', + 'resource_type.h', + 'scoped_clipboard_writer_glue.h', + 'simple_webmimeregistry_impl.cc', + 'simple_webmimeregistry_impl.h', + 'site_isolation_metrics.cc', + 'site_isolation_metrics.h', + 'webaccessibility.cc', + 'webaccessibility.h', + 'webclipboard_impl.cc', + 'webclipboard_impl.h', + 'webcookie.h', + 'webcursor.cc', + 'webcursor.h', + 'webcursor_gtk.cc', + 'webcursor_gtk_data.h', + 'webcursor_mac.mm', + 'webcursor_win.cc', + 'webdropdata.cc', + 'webdropdata_win.cc', + 'webdropdata.h', + 'webfilesystem_impl.cc', + 'webfilesystem_impl.h', + 'webkit_glue.cc', + 'webkit_glue.h', + 'webkitclient_impl.cc', + 'webkitclient_impl.h', + 'webmediaplayer_impl.h', + 'webmediaplayer_impl.cc', + 'webmenuitem.h', + 'webmenurunner_mac.h', + 'webmenurunner_mac.mm', + 'webpasswordautocompletelistener_impl.cc', + 'webpasswordautocompletelistener_impl.h', + 'webpreferences.cc', + 'webpreferences.h', + 'websocketstreamhandle_bridge.h', + 'websocketstreamhandle_delegate.h', + 'websocketstreamhandle_impl.cc', + 'websocketstreamhandle_impl.h', + 'webthemeengine_impl_win.cc', + 'weburlloader_impl.cc', + 'weburlloader_impl.h', + 'window_open_disposition.h', + 'window_open_disposition.cc', + + # These files used to be built in the webcore target, but moved here + # since part of glue. + '../extensions/v8/benchmarking_extension.cc', + '../extensions/v8/benchmarking_extension.h', + '../extensions/v8/gc_extension.cc', + '../extensions/v8/gc_extension.h', + '../extensions/v8/gears_extension.cc', + '../extensions/v8/gears_extension.h', + '../extensions/v8/heap_profiler_extension.cc', + '../extensions/v8/heap_profiler_extension.h', + '../extensions/v8/interval_extension.cc', + '../extensions/v8/interval_extension.h', + '../extensions/v8/playback_extension.cc', + '../extensions/v8/playback_extension.h', + '../extensions/v8/profiler_extension.cc', + '../extensions/v8/profiler_extension.h', + + ], + # When glue is a dependency, it needs to be a hard dependency. + # Dependents may rely on files generated by this target or one of its + # own hard dependencies. + 'hard_dependency': 1, + 'conditions': [ + ['OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', { + 'dependencies': [ + '<(DEPTH)/build/linux/system.gyp:gtk', + ], + 'sources!': [ + 'plugins/plugin_stubs.cc', + ], + }, { # else: OS!="linux" and OS!="freebsd" and OS!="openbsd" \ + # and OS!="solaris"' + 'sources/': [['exclude', '_(linux|gtk)(_data)?\\.cc$'], + ['exclude', r'/gtk_']], + }], + ['OS!="mac"', { + 'sources/': [['exclude', '_mac\\.(cc|mm)$']], + }, { # else: OS=="mac" + 'sources/': [['exclude', 'plugin_(lib|list)_posix\\.cc$']], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/QuartzCore.framework', + ], + }, + }], + ['enable_gpu==1 and inside_chromium_build==1', { + 'dependencies': [ + '<(DEPTH)/gpu/gpu.gyp:gpu_plugin', + ], + }], + ['OS!="win"', { + 'sources/': [['exclude', '_win\\.cc$']], + 'sources!': [ + 'webthemeengine_impl_win.cc', + ], + }, { # else: OS=="win" + 'sources/': [['exclude', '_posix\\.cc$']], + 'include_dirs': [ + '<(DEPTH)/third_party/wtl/include', + ], + 'dependencies': [ + '<(DEPTH)/build/win/system.gyp:cygwin', + ], + 'sources!': [ + 'plugins/plugin_stubs.cc', + ], + 'conditions': [ + ['inside_chromium_build==1 and component=="shared_library"', { + 'dependencies': [ + '<(DEPTH)/third_party/WebKit/WebKit/chromium/WebKit.gyp:webkit', + '<(DEPTH)/v8/tools/gyp/v8.gyp:v8', + ], + }], + ], + }], + ['inside_chromium_build==0', { + 'dependencies': [ + '<(DEPTH)/webkit/support/setup_third_party.gyp:third_party_headers', + ], + }], + ], + }, + ], +} diff --git a/webkit/glue/webkit_glue.h b/webkit/glue/webkit_glue.h new file mode 100644 index 0000000..541836b --- /dev/null +++ b/webkit/glue/webkit_glue.h @@ -0,0 +1,293 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBKIT_GLUE_H_ +#define WEBKIT_GLUE_WEBKIT_GLUE_H_ + +#include "base/basictypes.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <string> +#include <vector> + +#include "app/clipboard/clipboard.h" +#include "base/file_path.h" +#include "base/string16.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCanvas.h" + +class GURL; +class SkBitmap; +struct WebPluginInfo; + +namespace base { +class StringPiece; +} + +namespace skia { +class PlatformCanvas; +} + +namespace WebKit { +class WebFrame; +class WebString; +class WebView; +} + +namespace webkit_glue { + + +//---- BEGIN FUNCTIONS IMPLEMENTED BY WEBKIT/GLUE ----------------------------- + +void SetJavaScriptFlags(const std::wstring& flags); + +// Turn on the logging for notImplemented() calls from WebCore. +void EnableWebCoreNotImplementedLogging(); + +// Returns the text of the document element. +std::wstring DumpDocumentText(WebKit::WebFrame* web_frame); + +// Returns the text of the document element and optionally its child frames. +// If recursive is false, this is equivalent to DumpDocumentText followed by +// a newline. If recursive is true, it recursively dumps all frames as text. +std::wstring DumpFramesAsText(WebKit::WebFrame* web_frame, bool recursive); + +// Returns the renderer's description of its tree (its externalRepresentation). +std::wstring DumpRenderer(WebKit::WebFrame* web_frame); + +// Fill the value of counter in the element specified by the id into +// counter_value. Return false when the specified id doesn't exist. +bool CounterValueForElementById(WebKit::WebFrame* web_frame, + const std::string& id, + std::wstring* counter_value); + +// Returns the number of page where the specified element will be put. +int PageNumberForElementById(WebKit::WebFrame* web_frame, + const std::string& id, + float page_width_in_pixels, + float page_height_in_pixels); + +// Returns the number of pages to be printed. +int NumberOfPages(WebKit::WebFrame* web_frame, + float page_width_in_pixels, + float page_height_in_pixels); + +// Returns a dump of the scroll position of the webframe. +std::wstring DumpFrameScrollPosition(WebKit::WebFrame* web_frame, + bool recursive); + +// Returns a dump of the given history state suitable for implementing the +// dumpBackForwardList command of the layoutTestController. +std::wstring DumpHistoryState(const std::string& history_state, int indent, + bool is_current); + +// Cleans up state left over from the previous test run. +void ResetBeforeTestRun(WebKit::WebView* view); + +// Returns the WebKit version (major.minor). +std::string GetWebKitVersion(); + +// Called to override the default user agent with a custom one. Call this +// before anyone actually asks for the user agent in order to prevent +// inconsistent behavior. +void SetUserAgent(const std::string& new_user_agent); + +// Returns the user agent to use for the given URL, which is usually the +// default user agent but may be overriden by a call to SetUserAgent() (which +// should be done at startup). +const std::string& GetUserAgent(const GURL& url); + +// Creates serialized state for the specified URL. This is a variant of +// HistoryItemToString (in glue_serialize) that is used during session restore +// if the saved state is empty. +std::string CreateHistoryStateForURL(const GURL& url); + +// Removes any form data state from the history state string |content_state|. +std::string RemoveFormDataFromHistoryState(const std::string& content_state); + +#ifndef NDEBUG +// Checks various important objects to see if there are any in memory, and +// calls AppendToLog with any leaked objects. Designed to be called on shutdown +void CheckForLeaks(); +#endif + +// Decodes the image from the data in |image_data| into |image|. +// Returns false if the image could not be decoded. +bool DecodeImage(const std::string& image_data, SkBitmap* image); + +// Tells the plugin thread to terminate the process forcefully instead of +// exiting cleanly. +void SetForcefullyTerminatePluginProcess(bool value); + +// Returns true if the plugin thread should terminate the process forcefully +// instead of exiting cleanly. +bool ShouldForcefullyTerminatePluginProcess(); + +// File path string conversions. +FilePath::StringType WebStringToFilePathString(const WebKit::WebString& str); +WebKit::WebString FilePathStringToWebString(const FilePath::StringType& str); +FilePath WebStringToFilePath(const WebKit::WebString& str); +WebKit::WebString FilePathToWebString(const FilePath& file_path); + +// Returns a WebCanvas pointer associated with the given Skia canvas. +WebKit::WebCanvas* ToWebCanvas(skia::PlatformCanvas*); + +// Returns the number of currently-active glyph pages this process is using. +// There can be many such pages (maps of 256 character -> glyph) so this is +// used to get memory usage statistics. +int GetGlyphPageCount(); + +// Methods to query and enable media cache. +// TODO(hclam): Remove these methods when the cache is stable enough. +bool IsMediaCacheEnabled(); +void SetMediaCacheEnabled(bool enabled); + +//---- END FUNCTIONS IMPLEMENTED BY WEBKIT/GLUE ------------------------------- + + +//---- BEGIN FUNCTIONS IMPLEMENTED BY EMBEDDER -------------------------------- + +// This function is called to request a prefetch of the entire URL, loading it +// into our cache for (expected) future needs. The given URL may NOT be in +// canonical form and it will NOT be null-terminated; use the length instead. +void PrecacheUrl(const char16* url, int url_length); + +// This function is called to add a line to the application's log file. +void AppendToLog(const char* filename, int line, const char* message); + +// Gather usage statistics from the in-memory cache and inform our host, if +// applicable. +void NotifyCacheStats(); + +// Glue to get resources from the embedder. + +// Gets a localized string given a message id. Returns an empty string if the +// message id is not found. +string16 GetLocalizedString(int message_id); + +// Returns the raw data for a resource. This resource must have been +// specified as BINDATA in the relevant .rc file. +base::StringPiece GetDataResource(int resource_id); + +#if defined(OS_WIN) +// Loads and returns a cursor. +HCURSOR LoadCursor(int cursor_id); +#endif + +// Glue to access the clipboard. + +// Get a clipboard that can be used to construct a ScopedClipboardWriterGlue. +Clipboard* ClipboardGetClipboard(); + +// Tests whether the clipboard contains a certain format +bool ClipboardIsFormatAvailable(const Clipboard::FormatType& format, + Clipboard::Buffer buffer); + +// Reads UNICODE text from the clipboard, if available. +void ClipboardReadText(Clipboard::Buffer buffer, string16* result); + +// Reads ASCII text from the clipboard, if available. +void ClipboardReadAsciiText(Clipboard::Buffer buffer, std::string* result); + +// Reads HTML from the clipboard, if available. +void ClipboardReadHTML(Clipboard::Buffer buffer, string16* markup, GURL* url); + +// Reads the available types from the clipboard, if available. +bool ClipboardReadAvailableTypes(Clipboard::Buffer buffer, + std::vector<string16>* types, + bool* contains_filenames); + +// Reads one type of data from the clipboard, if available. +bool ClipboardReadData(Clipboard::Buffer buffer, const string16& type, + string16* data, string16* metadata); + +// Reads filenames from the clipboard, if available. +bool ClipboardReadFilenames(Clipboard::Buffer buffer, + std::vector<string16>* filenames); + +// Gets the directory where the application data and libraries exist. This +// may be a versioned subdirectory, or it may be the same directory as the +// GetExeDirectory(), depending on the embedder's implementation. +// Path is an output parameter to receive the path. +// Returns true if successful, false otherwise. +bool GetApplicationDirectory(FilePath* path); + +// Gets the directory where the launching executable resides on disk. +// Path is an output parameter to receive the path. +// Returns true if successful, false otherwise. +bool GetExeDirectory(FilePath* path); + +// Embedders implement this function to return the list of plugins to Webkit. +void GetPlugins(bool refresh, std::vector<WebPluginInfo>* plugins); + +// Returns true if the plugins run in the same process as the renderer, and +// false otherwise. +bool IsPluginRunningInRendererProcess(); + +// Returns a bool indicating if the Null plugin should be enabled or not. +bool IsDefaultPluginEnabled(); + +// Returns true if the protocol implemented to serve |url| supports features +// required by the media engine. +bool IsProtocolSupportedForMedia(const GURL& url); + +#if defined(OS_WIN) +// Downloads the file specified by the URL. On sucess a WM_COPYDATA message +// will be sent to the caller_window. +bool DownloadUrl(const std::string& url, HWND caller_window); +#endif + +// Returns the plugin finder URL. +bool GetPluginFinderURL(std::string* plugin_finder_url); + +// Resolves the proxies for the url, returns true on success. +bool FindProxyForUrl(const GURL& url, std::string* proxy_list); + +// Returns the locale that this instance of webkit is running as. This is of +// the form language-country (e.g., en-US or pt-BR). +std::wstring GetWebKitLocale(); + +// Close current connections. Used for debugging. +void CloseCurrentConnections(); + +// Enable or disable the disk cache. Used for debugging. +void SetCacheMode(bool enabled); + +// Clear the disk cache. Used for debugging. +void ClearCache(); + +// Returns the product version. E.g., Chrome/4.1.333.0 +std::string GetProductVersion(); + +// Returns true if the embedder is running in single process mode. +bool IsSingleProcess(); + +#if defined(OS_LINUX) +// Return a read-only file descriptor to the font which best matches the given +// properties or -1 on failure. +// charset: specifies the language(s) that the font must cover. See +// render_sandbox_host_linux.cc for more information. +int MatchFontWithFallback(const std::string& face, bool bold, + bool italic, int charset); + +// GetFontTable loads a specified font table from an open SFNT file. +// fd: a file descriptor to the SFNT file. The position doesn't matter. +// table: the table in *big-endian* format, or 0 for the whole font file. +// output: a buffer of size output_length that gets the data. can be 0, in +// which case output_length will be set to the required size in bytes. +// output_length: size of output, if it's not 0. +// +// returns: true on success. +bool GetFontTable(int fd, uint32_t table, uint8_t* output, + size_t* output_length); +#endif + +// ---- END FUNCTIONS IMPLEMENTED BY EMBEDDER --------------------------------- + + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBKIT_GLUE_H_ diff --git a/webkit/glue/webkit_glue_dummy.cc b/webkit/glue/webkit_glue_dummy.cc new file mode 100644 index 0000000..9947c3c --- /dev/null +++ b/webkit/glue/webkit_glue_dummy.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/webkit_glue.h" + + +//------------------------------------------------------------------------------ +// webkit_glue impl: + +namespace webkit_glue { + +// Global variable used by the plugin quirk "die after unload". +bool g_forcefully_terminate_plugin_process = false; + +void SetUserAgent(const std::string& new_user_agent) { +} + + +} // namespace webkit_glue diff --git a/webkit/glue/webkit_glue_unittest.cc b/webkit/glue/webkit_glue_unittest.cc new file mode 100644 index 0000000..9ba56e3 --- /dev/null +++ b/webkit/glue/webkit_glue_unittest.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "webkit/glue/webkit_glue.h" + +namespace { + +TEST(WebkitGlueTest, DecodeImageFail) { + std::string data("not an image"); + SkBitmap image; + EXPECT_FALSE(webkit_glue::DecodeImage(data, &image)); + EXPECT_TRUE(image.isNull()); +} + +TEST(WebkitGlueTest, DecodeImage) { + std::string data("GIF87a\x02\x00\x02\x00\xa1\x04\x00\x00\x00\x00\x00\x00\xff" + "\xff\x00\x00\x00\xff\x00,\x00\x00\x00\x00\x02\x00\x02\x00" + "\x00\x02\x03\x84\x16\x05\x00;", 42); + EXPECT_EQ(42u, data.size()); + SkBitmap image; + EXPECT_TRUE(webkit_glue::DecodeImage(data, &image)); + EXPECT_FALSE(image.isNull()); + EXPECT_EQ(2, image.width()); + EXPECT_EQ(2, image.height()); + EXPECT_EQ(SkBitmap::kARGB_8888_Config, image.config()); + image.lockPixels(); + EXPECT_EQ(SK_ColorBLACK, *image.getAddr32(0, 0)); + EXPECT_EQ(SK_ColorRED, *image.getAddr32(1, 0)); + EXPECT_EQ(SK_ColorGREEN, *image.getAddr32(0, 1)); + EXPECT_EQ(SK_ColorBLUE, *image.getAddr32(1, 1)); + image.unlockPixels(); +} + +} // namespace diff --git a/webkit/glue/webkit_resources.grd b/webkit/glue/webkit_resources.grd new file mode 100644 index 0000000..a2146fd --- /dev/null +++ b/webkit/glue/webkit_resources.grd @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit latest_public_release="0" current_release="1"> + <outputs> + <output filename="grit/webkit_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="webkit_resources.pak" type="data_package" /> + <output filename="webkit_resources.rc" type="rc_all" /> + </outputs> + <release seq="1"> + <includes> + <include name="IDC_ALIAS" file="resources\aliasb.cur" type="CURSOR" /> + <include name="IDR_BROKENIMAGE" file="resources\broken-image.gif" type="BINDATA" /> + <include name="IDC_CELL" file="resources\cell.cur" type="CURSOR" /> + <include name="IDC_COLRESIZE" file="resources\col_resize.cur" type="CURSOR" /> + <include name="IDC_COPYCUR" file="resources\copy.cur" type="CURSOR" /> + <include name="IDR_MEDIA_PAUSE_BUTTON" file="resources\media_pause.png" type="BINDATA" /> + <include name="IDR_MEDIA_PLAY_BUTTON" file="resources\media_play.png" type="BINDATA" /> + <include name="IDR_MEDIA_PLAY_BUTTON_DISABLED" file="resources\media_play_disabled.png" type="BINDATA" /> + <include name="IDR_MEDIA_SOUND_DISABLED" file="resources\media_sound_disabled.png" type="BINDATA" /> + <include name="IDR_MEDIA_SOUND_FULL_BUTTON" file="resources\media_sound_full.png" type="BINDATA" /> + <include name="IDR_MEDIA_SOUND_NONE_BUTTON" file="resources\media_sound_none.png" type="BINDATA" /> + <include name="IDR_MEDIA_SLIDER_THUMB" file="resources\media_slider_thumb.png" type="BINDATA" /> + <include name="IDR_MEDIA_VOLUME_SLIDER_THUMB" file="resources\media_volume_slider_thumb.png" type="BINDATA" /> + <include name="IDC_PAN_EAST" file="resources\pan_east.cur" type="CURSOR" /> + <include name="IDC_PAN_MIDDLE" file="resources\pan_middle.cur" type="CURSOR" /> + <include name="IDC_PAN_NORTH" file="resources\pan_north.cur" type="CURSOR" /> + <include name="IDC_PAN_NORTH_EAST" file="resources\pan_north_east.cur" type="CURSOR" /> + <include name="IDC_PAN_NORTH_WEST" file="resources\pan_north_west.cur" type="CURSOR" /> + <include name="IDR_PAN_SCROLL_ICON" file="resources\pan_icon.png" type="BINDATA" /> + <include name="IDC_PAN_SOUTH" file="resources\pan_south.cur" type="CURSOR" /> + <include name="IDC_PAN_SOUTH_EAST" file="resources\pan_south_east.cur" type="CURSOR" /> + <include name="IDC_PAN_SOUTH_WEST" file="resources\pan_south_west.cur" type="CURSOR" /> + <include name="IDC_PAN_WEST" file="resources\pan_west.cur" type="CURSOR" /> + <include name="IDC_ROWRESIZE" file="resources\row_resize.cur" type="CURSOR" /> + <include name="IDR_SEARCH_CANCEL" file="resources\search_cancel.png" type="BINDATA" /> + <include name="IDR_SEARCH_CANCEL_PRESSED" file="resources\search_cancel_pressed.png" type="BINDATA" /> + <include name="IDR_SEARCH_MAGNIFIER" file="resources\search_magnifier.png" type="BINDATA" /> + <include name="IDR_SEARCH_MAGNIFIER_RESULTS" file="resources\search_magnifier_results.png" type="BINDATA" /> + <include name="IDR_TEXTAREA_RESIZER" file="resources\textarea_resize_corner.png" type="BINDATA" /> + <include name="IDR_TICKMARK_DASH" file="resources\dash.png" type="BINDATA" /> + <include name="IDC_VERTICALTEXT" file="resources\vertical_text.cur" type="CURSOR" /> + <include name="IDC_ZOOMIN" file="resources\zoom_in.cur" type="CURSOR" /> + <include name="IDC_ZOOMOUT" file="resources\zoom_out.cur" type="CURSOR" /> + + <if expr="os == 'linux2' or os.find('bsd') != -1 or os == 'sunos5'"> + <include name="IDR_LINUX_CHECKBOX_DISABLED_INDETERMINATE" file="resources\linux-checkbox-disabled-indeterminate.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_DISABLED_OFF" file="resources\linux-checkbox-disabled-off.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_DISABLED_ON" file="resources\linux-checkbox-disabled-on.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_INDETERMINATE" file="resources\linux-checkbox-indeterminate.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_OFF" file="resources\linux-checkbox-off.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_ON" file="resources\linux-checkbox-on.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_DISABLED_OFF" file="resources\linux-radio-disabled-off.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_DISABLED_ON" file="resources\linux-radio-disabled-on.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_OFF" file="resources\linux-radio-off.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_ON" file="resources\linux-radio-on.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BAR" file="resources\linux-progress-bar.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BORDER_LEFT" file="resources\linux-progress-border-left.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BORDER_RIGHT" file="resources\linux-progress-border-right.png" type="BINDATA" /> + <include name="IDR_PROGRESS_VALUE" file="resources\linux-progress-value.png" type="BINDATA" /> + </if> + </includes> + </release> +</grit> diff --git a/webkit/glue/webkit_strings.grd b/webkit/glue/webkit_strings.grd new file mode 100644 index 0000000..cbb1c7f --- /dev/null +++ b/webkit/glue/webkit_strings.grd @@ -0,0 +1,364 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- This file contains definitions of resources that will be translated for +each locale. Specifically, these are UI strings that are used by webkit that +need to be translated for each locale.--> + +<!-- Some of these strings and string descriptions were taken from +WebKit/win/WebCoreLocalizedStrings.cpp so we include the original license +below: + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +--> + +<grit base_dir="." latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <!-- TODO add each of your output files. Modify the three below, and add + your own for your various languages. See the user's guide + (http://wiki/Main/GritUsersGuide) for more details. + Note that all output references are relative to the output directory + which is specified at build time. --> + <output filename="grit/webkit_strings.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="webkit_strings_am.rc" type="rc_all" lang="am" /> + <output filename="webkit_strings_ar.rc" type="rc_all" lang="ar" /> + <output filename="webkit_strings_bg.rc" type="rc_all" lang="bg" /> + <output filename="webkit_strings_bn.rc" type="rc_all" lang="bn" /> + <output filename="webkit_strings_ca.rc" type="rc_all" lang="ca" /> + <output filename="webkit_strings_cs.rc" type="rc_all" lang="cs" /> + <output filename="webkit_strings_da.rc" type="rc_all" lang="da" /> + <output filename="webkit_strings_de.rc" type="rc_all" lang="de" /> + <output filename="webkit_strings_el.rc" type="rc_all" lang="el" /> + <output filename="webkit_strings_en-GB.rc" type="rc_all" lang="en-GB" /> + <output filename="webkit_strings_en-US.rc" type="rc_all" lang="en" /> + <output filename="webkit_strings_es.rc" type="rc_all" lang="es" /> + <output filename="webkit_strings_es-419.rc" type="rc_all" lang="es-419" /> + <output filename="webkit_strings_et.rc" type="rc_all" lang="et" /> + <output filename="webkit_strings_fi.rc" type="rc_all" lang="fi" /> + <output filename="webkit_strings_fil.rc" type="rc_all" lang="fil" /> + <output filename="webkit_strings_fr.rc" type="rc_all" lang="fr" /> + <output filename="webkit_strings_gu.rc" type="rc_all" lang="gu" /> + <output filename="webkit_strings_he.rc" type="rc_all" lang="he" /> + <output filename="webkit_strings_hi.rc" type="rc_all" lang="hi" /> + <output filename="webkit_strings_hr.rc" type="rc_all" lang="hr" /> + <output filename="webkit_strings_hu.rc" type="rc_all" lang="hu" /> + <output filename="webkit_strings_id.rc" type="rc_all" lang="id" /> + <output filename="webkit_strings_it.rc" type="rc_all" lang="it" /> + <output filename="webkit_strings_ja.rc" type="rc_all" lang="ja" /> + <output filename="webkit_strings_kn.rc" type="rc_all" lang="kn" /> + <output filename="webkit_strings_ko.rc" type="rc_all" lang="ko" /> + <output filename="webkit_strings_lt.rc" type="rc_all" lang="lt" /> + <output filename="webkit_strings_lv.rc" type="rc_all" lang="lv" /> + <output filename="webkit_strings_ml.rc" type="rc_all" lang="ml" /> + <output filename="webkit_strings_mr.rc" type="rc_all" lang="mr" /> + <output filename="webkit_strings_nl.rc" type="rc_all" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="webkit_strings_nb.rc" type="rc_all" lang="no" /> + <output filename="webkit_strings_pl.rc" type="rc_all" lang="pl" /> + <output filename="webkit_strings_pt-BR.rc" type="rc_all" lang="pt-BR" /> + <output filename="webkit_strings_pt-PT.rc" type="rc_all" lang="pt-PT" /> + <output filename="webkit_strings_ro.rc" type="rc_all" lang="ro" /> + <output filename="webkit_strings_ru.rc" type="rc_all" lang="ru" /> + <output filename="webkit_strings_sk.rc" type="rc_all" lang="sk" /> + <output filename="webkit_strings_sl.rc" type="rc_all" lang="sl" /> + <output filename="webkit_strings_sr.rc" type="rc_all" lang="sr" /> + <output filename="webkit_strings_sv.rc" type="rc_all" lang="sv" /> + <output filename="webkit_strings_sw.rc" type="rc_all" lang="sw" /> + <output filename="webkit_strings_ta.rc" type="rc_all" lang="ta" /> + <output filename="webkit_strings_te.rc" type="rc_all" lang="te" /> + <output filename="webkit_strings_th.rc" type="rc_all" lang="th" /> + <output filename="webkit_strings_tr.rc" type="rc_all" lang="tr" /> + <output filename="webkit_strings_uk.rc" type="rc_all" lang="uk" /> + <output filename="webkit_strings_vi.rc" type="rc_all" lang="vi" /> + <output filename="webkit_strings_zh-CN.rc" type="rc_all" lang="zh-CN" /> + <output filename="webkit_strings_zh-TW.rc" type="rc_all" lang="zh-TW" /> + + <output filename="webkit_strings_am.pak" type="data_package" lang="am" /> + <output filename="webkit_strings_ar.pak" type="data_package" lang="ar" /> + <output filename="webkit_strings_bg.pak" type="data_package" lang="bg" /> + <output filename="webkit_strings_bn.pak" type="data_package" lang="bn" /> + <output filename="webkit_strings_ca.pak" type="data_package" lang="ca" /> + <output filename="webkit_strings_cs.pak" type="data_package" lang="cs" /> + <output filename="webkit_strings_da.pak" type="data_package" lang="da" /> + <output filename="webkit_strings_de.pak" type="data_package" lang="de" /> + <output filename="webkit_strings_el.pak" type="data_package" lang="el" /> + <output filename="webkit_strings_en-GB.pak" type="data_package" lang="en-GB" /> + <output filename="webkit_strings_en-US.pak" type="data_package" lang="en" /> + <output filename="webkit_strings_es.pak" type="data_package" lang="es" /> + <output filename="webkit_strings_es-419.pak" type="data_package" lang="es-419" /> + <output filename="webkit_strings_et.pak" type="data_package" lang="et" /> + <output filename="webkit_strings_fi.pak" type="data_package" lang="fi" /> + <output filename="webkit_strings_fil.pak" type="data_package" lang="fil" /> + <output filename="webkit_strings_fr.pak" type="data_package" lang="fr" /> + <output filename="webkit_strings_gu.pak" type="data_package" lang="gu" /> + <output filename="webkit_strings_he.pak" type="data_package" lang="he" /> + <output filename="webkit_strings_hi.pak" type="data_package" lang="hi" /> + <output filename="webkit_strings_hr.pak" type="data_package" lang="hr" /> + <output filename="webkit_strings_hu.pak" type="data_package" lang="hu" /> + <output filename="webkit_strings_id.pak" type="data_package" lang="id" /> + <output filename="webkit_strings_it.pak" type="data_package" lang="it" /> + <output filename="webkit_strings_ja.pak" type="data_package" lang="ja" /> + <output filename="webkit_strings_kn.pak" type="data_package" lang="kn" /> + <output filename="webkit_strings_ko.pak" type="data_package" lang="ko" /> + <output filename="webkit_strings_lt.pak" type="data_package" lang="lt" /> + <output filename="webkit_strings_lv.pak" type="data_package" lang="lv" /> + <output filename="webkit_strings_ml.pak" type="data_package" lang="ml" /> + <output filename="webkit_strings_mr.pak" type="data_package" lang="mr" /> + <output filename="webkit_strings_nl.pak" type="data_package" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="webkit_strings_nb.pak" type="data_package" lang="no" /> + <output filename="webkit_strings_pl.pak" type="data_package" lang="pl" /> + <output filename="webkit_strings_pt-BR.pak" type="data_package" lang="pt-BR" /> + <output filename="webkit_strings_pt-PT.pak" type="data_package" lang="pt-PT" /> + <output filename="webkit_strings_ro.pak" type="data_package" lang="ro" /> + <output filename="webkit_strings_ru.pak" type="data_package" lang="ru" /> + <output filename="webkit_strings_sk.pak" type="data_package" lang="sk" /> + <output filename="webkit_strings_sl.pak" type="data_package" lang="sl" /> + <output filename="webkit_strings_sr.pak" type="data_package" lang="sr" /> + <output filename="webkit_strings_sv.pak" type="data_package" lang="sv" /> + <output filename="webkit_strings_sw.pak" type="data_package" lang="sw" /> + <output filename="webkit_strings_ta.pak" type="data_package" lang="ta" /> + <output filename="webkit_strings_te.pak" type="data_package" lang="te" /> + <output filename="webkit_strings_th.pak" type="data_package" lang="th" /> + <output filename="webkit_strings_tr.pak" type="data_package" lang="tr" /> + <output filename="webkit_strings_uk.pak" type="data_package" lang="uk" /> + <output filename="webkit_strings_vi.pak" type="data_package" lang="vi" /> + <output filename="webkit_strings_zh-CN.pak" type="data_package" lang="zh-CN" /> + <output filename="webkit_strings_zh-TW.pak" type="data_package" lang="zh-TW" /> + </outputs> + <translations> + <file path="resources/webkit_strings_am.xtb" lang="am" /> + <file path="resources/webkit_strings_ar.xtb" lang="ar" /> + <file path="resources/webkit_strings_bg.xtb" lang="bg" /> + <file path="resources/webkit_strings_bn.xtb" lang="bn" /> + <file path="resources/webkit_strings_ca.xtb" lang="ca" /> + <file path="resources/webkit_strings_cs.xtb" lang="cs" /> + <file path="resources/webkit_strings_da.xtb" lang="da" /> + <file path="resources/webkit_strings_de.xtb" lang="de" /> + <file path="resources/webkit_strings_el.xtb" lang="el" /> + <file path="resources/webkit_strings_en-GB.xtb" lang="en-GB" /> + <file path="resources/webkit_strings_es.xtb" lang="es" /> + <file path="resources/webkit_strings_es-419.xtb" lang="es-419" /> + <file path="resources/webkit_strings_et.xtb" lang="et" /> + <file path="resources/webkit_strings_fi.xtb" lang="fi" /> + <file path="resources/webkit_strings_fil.xtb" lang="fil" /> + <file path="resources/webkit_strings_fr.xtb" lang="fr" /> + <file path="resources/webkit_strings_gu.xtb" lang="gu" /> + <file path="resources/webkit_strings_hi.xtb" lang="hi" /> + <file path="resources/webkit_strings_hr.xtb" lang="hr" /> + <file path="resources/webkit_strings_hu.xtb" lang="hu" /> + <file path="resources/webkit_strings_id.xtb" lang="id" /> + <file path="resources/webkit_strings_it.xtb" lang="it" /> + <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. --> + <file path="resources/webkit_strings_iw.xtb" lang="he" /> + <file path="resources/webkit_strings_ja.xtb" lang="ja" /> + <file path="resources/webkit_strings_kn.xtb" lang="kn" /> + <file path="resources/webkit_strings_ko.xtb" lang="ko" /> + <file path="resources/webkit_strings_lt.xtb" lang="lt" /> + <file path="resources/webkit_strings_lv.xtb" lang="lv" /> + <file path="resources/webkit_strings_ml.xtb" lang="ml" /> + <file path="resources/webkit_strings_mr.xtb" lang="mr" /> + <file path="resources/webkit_strings_nl.xtb" lang="nl" /> + <file path="resources/webkit_strings_no.xtb" lang="no" /> + <file path="resources/webkit_strings_pl.xtb" lang="pl" /> + <file path="resources/webkit_strings_pt-BR.xtb" lang="pt-BR" /> + <file path="resources/webkit_strings_pt-PT.xtb" lang="pt-PT" /> + <file path="resources/webkit_strings_ro.xtb" lang="ro" /> + <file path="resources/webkit_strings_ru.xtb" lang="ru" /> + <file path="resources/webkit_strings_sk.xtb" lang="sk" /> + <file path="resources/webkit_strings_sl.xtb" lang="sl" /> + <file path="resources/webkit_strings_sr.xtb" lang="sr" /> + <file path="resources/webkit_strings_sv.xtb" lang="sv" /> + <file path="resources/webkit_strings_sw.xtb" lang="sw" /> + <file path="resources/webkit_strings_ta.xtb" lang="ta" /> + <file path="resources/webkit_strings_te.xtb" lang="te" /> + <file path="resources/webkit_strings_th.xtb" lang="th" /> + <file path="resources/webkit_strings_tr.xtb" lang="tr" /> + <file path="resources/webkit_strings_uk.xtb" lang="uk" /> + <file path="resources/webkit_strings_vi.xtb" lang="vi" /> + <file path="resources/webkit_strings_zh-CN.xtb" lang="zh-CN" /> + <file path="resources/webkit_strings_zh-TW.xtb" lang="zh-TW" /> + </translations> + <release seq="1" allow_pseudo="false"> + <messages fallback_to_english="true"> + <!-- TODO add all of your "string table" messages here. Remember to + change nontranslateable parts of the messages into placeholders (using the + <ph> element). You can also use the 'grit add' tool to help you identify + nontranslateable parts and create placeholders for them. --> + + <message name="IDS_SEARCHABLE_INDEX_INTRO" desc="Text that appears at the start of nearly-obsolete web pages in the form of a 'searchable index'."> + This is a searchable index. Enter search keywords: ''' + </message> + <message name="IDS_FORM_SUBMIT_LABEL" desc="Default label for Submit buttons in forms on web pages."> + Submit + </message> + <message name="IDS_FORM_INPUT_ALT" desc="alt text for <input> elements with no alt, title, or value"> + Submit + </message> + <message name="IDS_FORM_RESET_LABEL" desc="default label for Reset buttons in forms on web pages"> + Reset + </message> + <message name="IDS_FORM_FILE_BUTTON_LABEL" desc="title for file button used in HTML forms"> + Choose File + </message> + <message name="IDS_FORM_FILE_NO_FILE_LABEL" desc="text to display in file button used in HTML forms when no file is selected"> + No file chosen + </message> + <message name="IDS_FORM_FILE_NO_FILE_DRAG_LABEL" desc="text to display in file button used in HTML forms when no file is selected to indicate that files can be dragged onto the file button"> + Drag file here + </message> + <message name="IDS_FORM_FILE_MULTIPLE_UPLOAD" desc="text to display next to file buttons in HTML forms when 2 or more files are selected for uploading. This is not used for a case that just 1 file is selected."> + <ph name="NUMBER_OF_FILES">$1<ex>3</ex></ph> files + </message> + + <message name="IDS_RECENT_SEARCHES_NONE" desc="Label for only item in menu that appears when clicking on the search field image, when no searches have been performed"> + No recent searches + </message> + <message name="IDS_RECENT_SEARCHES" desc="label for first item in the menu that appears when clicking on the search field image, used as embedded menu title"> + Recent Searches + </message> + <message name="IDS_RECENT_SEARCHES_CLEAR" desc="menu item in Recent Searches menu that empties menu's contents"> + Clear Recent Searches + </message> + + <message name="IDS_IMAGE_TITLE_FOR_FILENAME" desc="window title for a standalone image (uses mutiplication symbol, not x)"> + <ph name="FILENAME">%s<ex>My Cool Image.gif</ex></ph><ph name="WIDTH">%d<ex>400</ex></ph>×<ph name="HEIGHT">%d<ex>600</ex></ph> + </message> + + <message name="IDS_AX_ROLE_WEB_AREA" desc="accessibility role description for web area"> + web area + </message> + <message name="IDS_AX_ROLE_LINK" desc="accessibility role description for link"> + link + </message> + <message name="IDS_AX_ROLE_LIST_MARKER" desc="accessibility role description for list marker"> + list marker + </message> + <message name="IDS_AX_ROLE_IMAGE_MAP" desc="accessibility role description for image map"> + image map + </message> + <message name="IDS_AX_ROLE_HEADING" desc="accessibility role description for headings"> + heading + </message> + + <message name="IDS_AX_BUTTON_ACTION_VERB" desc="Verb stating the action that will occur when a button is pressed, as used by accessibility."> + press + </message> + <message name="IDS_AX_RADIO_BUTTON_ACTION_VERB" desc="Verb stating the action that will occur when a radio button is clicked, as used by accessibility."> + select + </message> + <message name="IDS_AX_TEXT_FIELD_ACTION_VERB" desc="Verb stating the action that will occur when a text field is selected, as used by accessibility."> + activate + </message> + <message name="IDS_AX_CHECKED_CHECK_BOX_ACTION_VERB" desc="Verb stating the action that will occur when a checked checkbox is clicked, as used by accessibility."> + uncheck + </message> + <message name="IDS_AX_UNCHECKED_CHECK_BOX_ACTION_VERB" desc="Verb stating the action that will occur when an unchecked checkbox is clicked, as used by accessibility."> + check + </message> + <message name="IDS_AX_LINK_ACTION_VERB" desc="Verb stating the action that will occur when a link is clicked, as used by accessibility."> + jump + </message> + + <message name="IDS_KEYGEN_HIGH_GRADE_KEY" desc="High-grade cryptographic key size menu item"> + 2048 (High Grade) + </message> + <message name="IDS_KEYGEN_MED_GRADE_KEY" desc="Medium-grade cryptographic key size menu item"> + 1024 (Medium Grade) + </message> + + <message name="IDS_DEFAULT_PLUGIN_GET_PLUGIN_MSG" desc="Message displayed by the default plugin in its main window"> + <ph name="PLUGIN">$1<ex>Realplayer</ex></ph> plug-in is not installed + </message> + + <message name="IDS_DEFAULT_PLUGIN_GET_PLUGIN_MSG_NO_PLUGIN_NAME" desc="Message displayed by the default plugin in its main window when we don't know the plugin name"> + The required plug-in is not installed + </message> + + <message name="IDS_DEFAULT_PLUGIN_GET_PLUGIN_MSG_2" desc="Second Message displayed by the default plugin in its main window"> + Click here to download plug-in + </message> + + <message name="IDS_DEFAULT_PLUGIN_REFRESH_PLUGIN_MSG" desc="Message displayed by the default plugin to refresh the window after installing the required plugin"> + After installing the plug-in, click here to refresh + </message> + + <message name="IDS_DEFAULT_PLUGIN_NO_PLUGIN_AVAILABLE_MSG" desc="Message displayed by the default plugin when no plugin was found for the page."> + No plug-in available to display this content + </message> + + <message name="IDS_DEFAULT_PLUGIN_DOWNLOADING_PLUGIN_MSG" desc="Message displayed by the default plugin when a download has been initiated for the third party plugin."> + Downloading plug-in... + </message> + + <message name="IDS_DEFAULT_PLUGIN_GET_THE_PLUGIN_BTN_MSG" desc="Message displayed by the default plugin on the button which the user should click to fetch the plugin."> + Get Plug-in + </message> + + <message name="IDS_DEFAULT_PLUGIN_CANCEL_PLUGIN_DOWNLOAD_MSG" desc="Message displayed by the default plugin on the button which the user should click to cancel the plugin download."> + Cancel + </message> + + <message name="IDS_DEFAULT_PLUGIN_CONFIRMATION_DIALOG_TITLE" desc="Default plugin confirmation dialog title."> + <ph name="PLUGIN">$1<ex>Realplayer</ex></ph> plug-in needed + </message> + + <message name="IDS_DEFAULT_PLUGIN_CONFIRMATION_DIALOG_TITLE_NO_PLUGIN_NAME" desc="Default plugin confirmation dialog title when we don't know the plugin name"> + Additional plug-in needed + </message> + + <message name="IDS_DEFAULT_PLUGIN_USER_OPTION_MSG" desc="Message displayed by the default plugin in the install dialog indicating information on the user action."> + Please confirm that you would like to install the <ph name="PLUGIN">$1<ex>realplayer</ex></ph> plug-in. You should only install plug-ins that you trust. + </message> + <message name="IDS_DEFAULT_PLUGIN_USER_OPTION_MSG_NO_PLUGIN_NAME" desc="Message displayed by the default plugin in the install dialog indicating information on the user action when we don't know the plugin name"> + Please confirm that you would like to install this plug-in. You should only install plug-ins that you trust. + </message> + + <message name="IDS_DEFAULT_PLUGIN_USE_OPTION_CONFIRM" desc="Button to confirm installation of plugin"> + Install + </message> + <message name="IDS_DEFAULT_PLUGIN_USE_OPTION_CANCEL" desc="Button to cancel installation of plugin"> + Cancel + </message> + + <message name="IDS_DEFAULT_PLUGIN_DOWNLOAD_FAILED_MSG" desc="Message displayed by the default plugin when download is failed."> + Failed to install plug-in from <ph name="URL">$1<ex>http://www.google.com/blablah.exe</ex></ph> + </message> + + <message name="IDS_DEFAULT_PLUGIN_INSTALLATION_FAILED_MSG" desc="Message displayed by the default plugin when installation is failed."> + Plug-in installation failed + </message> + + <message name="IDS_PDF_NEED_PASSWORD" desc="A message asking the user for a password to open a PDF file."> + This document is password protected. Please enter a password. + </message> + </messages> + </release> +</grit> diff --git a/webkit/glue/webkitclient_impl.cc b/webkit/glue/webkitclient_impl.cc new file mode 100644 index 0000000..b05e7d9 --- /dev/null +++ b/webkit/glue/webkitclient_impl.cc @@ -0,0 +1,437 @@ +// Copyright (c) 2010 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 "webkit/glue/webkitclient_impl.h" + +#if defined(OS_LINUX) +#include <malloc.h> +#endif + +#include <math.h> + +#include <vector> + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/platform_file.h" +#include "base/singleton.h" +#include "base/stats_counters.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/trace_event.h" +#include "grit/webkit_resources.h" +#include "grit/webkit_strings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebCookie.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrameClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPluginListBuilder.h" +#include "third_party/WebKit/WebKit/chromium/public/WebScreenInfo.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebVector.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "webkit/glue/plugins/plugin_instance.h" +#include "webkit/glue/plugins/webplugininfo.h" +#include "webkit/glue/webkit_glue.h" +#include "webkit/glue/websocketstreamhandle_impl.h" +#include "webkit/glue/weburlloader_impl.h" + +#if defined(OS_LINUX) +#include "v8/include/v8.h" +#endif + +using WebKit::WebCookie; +using WebKit::WebData; +using WebKit::WebLocalizedString; +using WebKit::WebPluginListBuilder; +using WebKit::WebString; +using WebKit::WebSocketStreamHandle; +using WebKit::WebThemeEngine; +using WebKit::WebURL; +using WebKit::WebURLLoader; +using WebKit::WebVector; + +namespace { + +// A simple class to cache the memory usage for a given amount of time. +class MemoryUsageCache { + public: + // Retrieves the Singleton. + static MemoryUsageCache* Get() { + return Singleton<MemoryUsageCache>::get(); + } + + MemoryUsageCache() : memory_value_(0) { Init(); } + ~MemoryUsageCache() {} + + void Init() { + const unsigned int kCacheSeconds = 1; + cache_valid_time_ = base::TimeDelta::FromSeconds(kCacheSeconds); + } + + // Returns true if the cached value is fresh. + // Returns false if the cached value is stale, or if |cached_value| is NULL. + bool IsCachedValueValid(size_t* cached_value) { + AutoLock scoped_lock(lock_); + if (!cached_value) + return false; + if (base::Time::Now() - last_updated_time_ > cache_valid_time_) + return false; + *cached_value = memory_value_; + return true; + }; + + // Setter for |memory_value_|, refreshes |last_updated_time_|. + void SetMemoryValue(const size_t value) { + AutoLock scoped_lock(lock_); + memory_value_ = value; + last_updated_time_ = base::Time::Now(); + } + + private: + // The cached memory value. + size_t memory_value_; + + // How long the cached value should remain valid. + base::TimeDelta cache_valid_time_; + + // The last time the cached value was updated. + base::Time last_updated_time_; + + Lock lock_; +}; + +} // anonymous namespace + +namespace webkit_glue { + +static int ToMessageID(WebLocalizedString::Name name) { + switch (name) { + case WebLocalizedString::SubmitButtonDefaultLabel: + return IDS_FORM_SUBMIT_LABEL; + case WebLocalizedString::InputElementAltText: + return IDS_FORM_INPUT_ALT; + case WebLocalizedString::ResetButtonDefaultLabel: + return IDS_FORM_RESET_LABEL; + case WebLocalizedString::FileButtonChooseFileLabel: + return IDS_FORM_FILE_BUTTON_LABEL; + case WebLocalizedString::FileButtonNoFileSelectedLabel: + return IDS_FORM_FILE_NO_FILE_LABEL; + case WebLocalizedString::MultipleFileUploadText: + return IDS_FORM_FILE_MULTIPLE_UPLOAD; + case WebLocalizedString::SearchableIndexIntroduction: + return IDS_SEARCHABLE_INDEX_INTRO; + case WebLocalizedString::SearchMenuNoRecentSearchesText: + return IDS_RECENT_SEARCHES_NONE; + case WebLocalizedString::SearchMenuRecentSearchesText: + return IDS_RECENT_SEARCHES; + case WebLocalizedString::SearchMenuClearRecentSearchesText: + return IDS_RECENT_SEARCHES_CLEAR; + case WebLocalizedString::AXWebAreaText: + return IDS_AX_ROLE_WEB_AREA; + case WebLocalizedString::AXLinkText: + return IDS_AX_ROLE_LINK; + case WebLocalizedString::AXListMarkerText: + return IDS_AX_ROLE_LIST_MARKER; + case WebLocalizedString::AXImageMapText: + return IDS_AX_ROLE_IMAGE_MAP; + case WebLocalizedString::AXHeadingText: + return IDS_AX_ROLE_HEADING; + case WebLocalizedString::AXButtonActionVerb: + return IDS_AX_BUTTON_ACTION_VERB; + case WebLocalizedString::AXRadioButtonActionVerb: + return IDS_AX_RADIO_BUTTON_ACTION_VERB; + case WebLocalizedString::AXTextFieldActionVerb: + return IDS_AX_TEXT_FIELD_ACTION_VERB; + case WebLocalizedString::AXCheckedCheckBoxActionVerb: + return IDS_AX_CHECKED_CHECK_BOX_ACTION_VERB; + case WebLocalizedString::AXUncheckedCheckBoxActionVerb: + return IDS_AX_UNCHECKED_CHECK_BOX_ACTION_VERB; + case WebLocalizedString::AXLinkActionVerb: + return IDS_AX_LINK_ACTION_VERB; + case WebLocalizedString::KeygenMenuHighGradeKeySize: + return IDS_KEYGEN_HIGH_GRADE_KEY; + case WebLocalizedString::KeygenMenuMediumGradeKeySize: + return IDS_KEYGEN_MED_GRADE_KEY; + } + return -1; +} + +WebKitClientImpl::WebKitClientImpl() + : main_loop_(MessageLoop::current()), + shared_timer_func_(NULL), + shared_timer_fire_time_(0.0), + shared_timer_suspended_(0) { +} + +WebThemeEngine* WebKitClientImpl::themeEngine() { +#if defined(OS_WIN) + return &theme_engine_; +#else + return NULL; +#endif +} + +WebURLLoader* WebKitClientImpl::createURLLoader() { + return new WebURLLoaderImpl(); +} + +WebSocketStreamHandle* WebKitClientImpl::createSocketStreamHandle() { + return new WebSocketStreamHandleImpl(); +} + +WebString WebKitClientImpl::userAgent(const WebURL& url) { + return WebString::fromUTF8(webkit_glue::GetUserAgent(url)); +} + +void WebKitClientImpl::getPluginList(bool refresh, + WebPluginListBuilder* builder) { + std::vector<WebPluginInfo> plugins; + GetPlugins(refresh, &plugins); + + for (size_t i = 0; i < plugins.size(); ++i) { + const WebPluginInfo& plugin = plugins[i]; + + builder->addPlugin( + plugin.name, plugin.desc, + FilePathStringToWebString(plugin.path.BaseName().value())); + + for (size_t j = 0; j < plugin.mime_types.size(); ++j) { + const WebPluginMimeType& mime_type = plugin.mime_types[j]; + + builder->addMediaTypeToLastPlugin( + WebString::fromUTF8(mime_type.mime_type), mime_type.description); + + for (size_t k = 0; k < mime_type.file_extensions.size(); ++k) { + builder->addFileExtensionToLastMediaType( + UTF8ToUTF16(mime_type.file_extensions[k])); + } + } + } +} + +void WebKitClientImpl::decrementStatsCounter(const char* name) { + StatsCounter(name).Decrement(); +} + +void WebKitClientImpl::incrementStatsCounter(const char* name) { + StatsCounter(name).Increment(); +} + +void WebKitClientImpl::traceEventBegin(const char* name, void* id, + const char* extra) { + TRACE_EVENT_BEGIN(name, id, extra); +} + +void WebKitClientImpl::traceEventEnd(const char* name, void* id, + const char* extra) { + TRACE_EVENT_END(name, id, extra); +} + +WebData WebKitClientImpl::loadResource(const char* name) { + struct { + const char* name; + int id; + } resources[] = { + { "missingImage", IDR_BROKENIMAGE }, + { "mediaPause", IDR_MEDIA_PAUSE_BUTTON }, + { "mediaPlay", IDR_MEDIA_PLAY_BUTTON }, + { "mediaPlayDisabled", IDR_MEDIA_PLAY_BUTTON_DISABLED }, + { "mediaSoundDisabled", IDR_MEDIA_SOUND_DISABLED }, + { "mediaSoundFull", IDR_MEDIA_SOUND_FULL_BUTTON }, + { "mediaSoundNone", IDR_MEDIA_SOUND_NONE_BUTTON }, + { "mediaSliderThumb", IDR_MEDIA_SLIDER_THUMB }, + { "mediaVolumeSliderThumb", IDR_MEDIA_VOLUME_SLIDER_THUMB }, + { "panIcon", IDR_PAN_SCROLL_ICON }, + { "searchCancel", IDR_SEARCH_CANCEL }, + { "searchCancelPressed", IDR_SEARCH_CANCEL_PRESSED }, + { "searchMagnifier", IDR_SEARCH_MAGNIFIER }, + { "searchMagnifierResults", IDR_SEARCH_MAGNIFIER_RESULTS }, + { "textAreaResizeCorner", IDR_TEXTAREA_RESIZER }, + { "tickmarkDash", IDR_TICKMARK_DASH }, +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // TODO(port): rename these to "skia" instead of "Linux". + { "linuxCheckboxDisabledIndeterminate", + IDR_LINUX_CHECKBOX_DISABLED_INDETERMINATE }, + { "linuxCheckboxDisabledOff", IDR_LINUX_CHECKBOX_DISABLED_OFF }, + { "linuxCheckboxDisabledOn", IDR_LINUX_CHECKBOX_DISABLED_ON }, + { "linuxCheckboxIndeterminate", IDR_LINUX_CHECKBOX_INDETERMINATE }, + { "linuxCheckboxOff", IDR_LINUX_CHECKBOX_OFF }, + { "linuxCheckboxOn", IDR_LINUX_CHECKBOX_ON }, + { "linuxRadioDisabledOff", IDR_LINUX_RADIO_DISABLED_OFF }, + { "linuxRadioDisabledOn", IDR_LINUX_RADIO_DISABLED_ON }, + { "linuxRadioOff", IDR_LINUX_RADIO_OFF }, + { "linuxRadioOn", IDR_LINUX_RADIO_ON }, + { "linuxProgressBar", IDR_PROGRESS_BAR }, + { "linuxProgressBorderLeft", IDR_PROGRESS_BORDER_LEFT }, + { "linuxProgressBorderRight", IDR_PROGRESS_BORDER_RIGHT }, + { "linuxProgressValue", IDR_PROGRESS_VALUE }, +#endif + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(resources); ++i) { + if (!strcmp(name, resources[i].name)) { + base::StringPiece resource = GetDataResource(resources[i].id); + return WebData(resource.data(), resource.size()); + } + } + NOTREACHED() << "Unknown image resource " << name; + return WebData(); +} + +WebString WebKitClientImpl::queryLocalizedString( + WebLocalizedString::Name name) { + int message_id = ToMessageID(name); + if (message_id < 0) + return WebString(); + return GetLocalizedString(message_id); +} + +WebString WebKitClientImpl::queryLocalizedString( + WebLocalizedString::Name name, int numeric_value) { + int message_id = ToMessageID(name); + if (message_id < 0) + return WebString(); + return ReplaceStringPlaceholders(GetLocalizedString(message_id), + IntToString16(numeric_value), + NULL); +} + +double WebKitClientImpl::currentTime() { + return base::Time::Now().ToDoubleT(); +} + +void WebKitClientImpl::setSharedTimerFiredFunction(void (*func)()) { + shared_timer_func_ = func; +} + +void WebKitClientImpl::setSharedTimerFireTime(double fire_time) { + shared_timer_fire_time_ = fire_time; + if (shared_timer_suspended_) + return; + + // By converting between double and int64 representation, we run the risk + // of losing precision due to rounding errors. Performing computations in + // microseconds reduces this risk somewhat. But there still is the potential + // of us computing a fire time for the timer that is shorter than what we + // need. + // As the event loop will check event deadlines prior to actually firing + // them, there is a risk of needlessly rescheduling events and of + // needlessly looping if sleep times are too short even by small amounts. + // This results in measurable performance degradation unless we use ceil() to + // always round up the sleep times. + int64 interval = static_cast<int64>( + ceil((fire_time - currentTime()) * base::Time::kMicrosecondsPerSecond)); + if (interval < 0) + interval = 0; + + shared_timer_.Stop(); + shared_timer_.Start(base::TimeDelta::FromMicroseconds(interval), this, + &WebKitClientImpl::DoTimeout); +} + +void WebKitClientImpl::stopSharedTimer() { + shared_timer_.Stop(); +} + +void WebKitClientImpl::callOnMainThread(void (*func)(void*), void* context) { + main_loop_->PostTask(FROM_HERE, NewRunnableFunction(func, context)); +} + +base::PlatformFile WebKitClientImpl::databaseOpenFile( + const WebKit::WebString& vfs_file_name, int desired_flags) { + return base::kInvalidPlatformFileValue; +} + +int WebKitClientImpl::databaseDeleteFile( + const WebKit::WebString& vfs_file_name, bool sync_dir) { + return -1; +} + +long WebKitClientImpl::databaseGetFileAttributes( + const WebKit::WebString& vfs_file_name) { + return 0; +} + +long long WebKitClientImpl::databaseGetFileSize( + const WebKit::WebString& vfs_file_name) { + return 0; +} + +WebKit::WebString WebKitClientImpl::signedPublicKeyAndChallengeString( + unsigned key_size_index, + const WebKit::WebString& challenge, + const WebKit::WebURL& url) { + NOTREACHED(); + return WebKit::WebString(); +} + +#if defined(OS_LINUX) +static size_t memoryUsageMBLinux() { + struct mallinfo minfo = mallinfo(); + uint64_t mem_usage = +#if defined(USE_TCMALLOC) + minfo.uordblks +#else + (minfo.hblkhd + minfo.arena) +#endif + >> 20; + + v8::HeapStatistics stat; + v8::V8::GetHeapStatistics(&stat); + return mem_usage + (static_cast<uint64_t>(stat.total_heap_size()) >> 20); +} +#endif + +#if defined(OS_MACOSX) +static size_t memoryUsageMBMac() { + using base::ProcessMetrics; + static ProcessMetrics* process_metrics = + // The default port provider is sufficient to get data for the current + // process. + ProcessMetrics::CreateProcessMetrics(base::GetCurrentProcessHandle(), + NULL); + DCHECK(process_metrics); + return process_metrics->GetPagefileUsage() >> 20; +} +#endif + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) +static size_t memoryUsageMBGeneric() { + using base::ProcessMetrics; + static ProcessMetrics* process_metrics = + ProcessMetrics::CreateProcessMetrics(base::GetCurrentProcessHandle()); + DCHECK(process_metrics); + return process_metrics->GetPagefileUsage() >> 20; +} +#endif + +size_t WebKitClientImpl::memoryUsageMB() { + size_t current_mem_usage = 0; + MemoryUsageCache* mem_usage_cache_singleton = MemoryUsageCache::Get(); + if (mem_usage_cache_singleton->IsCachedValueValid(¤t_mem_usage)) + return current_mem_usage; + + current_mem_usage = +#if defined(OS_LINUX) + memoryUsageMBLinux(); +#elif defined(OS_MACOSX) + memoryUsageMBMac(); +#else + memoryUsageMBGeneric(); +#endif + mem_usage_cache_singleton->SetMemoryValue(current_mem_usage); + return current_mem_usage; +} + +void WebKitClientImpl::SuspendSharedTimer() { + ++shared_timer_suspended_; +} + +void WebKitClientImpl::ResumeSharedTimer() { + // The shared timer may have fired or been adjusted while we were suspended. + if (--shared_timer_suspended_ == 0 && !shared_timer_.IsRunning()) + setSharedTimerFireTime(shared_timer_fire_time_); +} + +} // namespace webkit_glue diff --git a/webkit/glue/webkitclient_impl.h b/webkit/glue/webkitclient_impl.h new file mode 100644 index 0000000..bec7a79 --- /dev/null +++ b/webkit/glue/webkitclient_impl.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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 WEBKIT_CLIENT_IMPL_H_ +#define WEBKIT_CLIENT_IMPL_H_ + +#include "base/platform_file.h" +#include "base/timer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKitClient.h" +#if defined(OS_WIN) +#include "webkit/glue/webthemeengine_impl_win.h" +#endif + +class MessageLoop; + +namespace webkit_glue { + +class WebKitClientImpl : public WebKit::WebKitClient { + public: + WebKitClientImpl(); + virtual ~WebKitClientImpl() {} + + // WebKitClient methods (partial implementation): + virtual WebKit::WebThemeEngine* themeEngine(); + + virtual base::PlatformFile databaseOpenFile( + const WebKit::WebString& vfs_file_name, int desired_flags); + virtual int databaseDeleteFile(const WebKit::WebString& vfs_file_name, + bool sync_dir); + virtual long databaseGetFileAttributes( + const WebKit::WebString& vfs_file_name); + virtual long long databaseGetFileSize(const WebKit::WebString& vfs_file_name); + virtual WebKit::WebString signedPublicKeyAndChallengeString( + unsigned key_size_index, const WebKit::WebString& challenge, + const WebKit::WebURL& url); + virtual size_t memoryUsageMB(); + virtual WebKit::WebURLLoader* createURLLoader(); + virtual WebKit::WebSocketStreamHandle* createSocketStreamHandle(); + virtual WebKit::WebString userAgent(const WebKit::WebURL& url); + virtual void getPluginList(bool refresh, WebKit::WebPluginListBuilder*); + virtual void decrementStatsCounter(const char* name); + virtual void incrementStatsCounter(const char* name); + virtual void traceEventBegin(const char* name, void* id, const char* extra); + virtual void traceEventEnd(const char* name, void* id, const char* extra); + virtual WebKit::WebData loadResource(const char* name); + virtual WebKit::WebString queryLocalizedString( + WebKit::WebLocalizedString::Name name); + virtual WebKit::WebString queryLocalizedString( + WebKit::WebLocalizedString::Name name, int numeric_value); + virtual void suddenTerminationChanged(bool enabled) { } + virtual double currentTime(); + virtual void setSharedTimerFiredFunction(void (*func)()); + virtual void setSharedTimerFireTime(double fireTime); + virtual void stopSharedTimer(); + virtual void callOnMainThread(void (*func)(void*), void* context); + + void SuspendSharedTimer(); + void ResumeSharedTimer(); + + private: + void DoTimeout() { + if (shared_timer_func_ && !shared_timer_suspended_) + shared_timer_func_(); + } + + MessageLoop* main_loop_; + base::OneShotTimer<WebKitClientImpl> shared_timer_; + void (*shared_timer_func_)(); + double shared_timer_fire_time_; + int shared_timer_suspended_; // counter + +#if defined(OS_WIN) + WebThemeEngineImpl theme_engine_; +#endif +}; + +} // namespace webkit_glue + +#endif // WEBKIT_CLIENT_IMPL_H_ diff --git a/webkit/glue/webmediaplayer_impl.cc b/webkit/glue/webmediaplayer_impl.cc new file mode 100644 index 0000000..51d67ab --- /dev/null +++ b/webkit/glue/webmediaplayer_impl.cc @@ -0,0 +1,711 @@ +// Copyright (c) 2010 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 "webkit/glue/webmediaplayer_impl.h" + +#include "base/callback.h" +#include "base/command_line.h" +#include "media/base/limits.h" +#include "media/base/media_format.h" +#include "media/base/media_switches.h" +#include "media/filters/ffmpeg_audio_decoder.h" +#include "media/filters/ffmpeg_demuxer.h" +#include "media/filters/ffmpeg_video_decoder.h" +#include "media/filters/null_audio_renderer.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSize.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "webkit/glue/media/video_renderer_impl.h" +#include "webkit/glue/media/web_video_renderer.h" + +using WebKit::WebCanvas; +using WebKit::WebRect; +using WebKit::WebSize; + +namespace { + +// Limits the maximum outstanding repaints posted on render thread. +// This number of 50 is a guess, it does not take too much memory on the task +// queue but gives up a pretty good latency on repaint. +const int kMaxOutstandingRepaints = 50; + +// Limits the range of playback rate. +// +// TODO(kylep): Revisit these. +// +// Vista has substantially lower performance than XP or Windows7. If you speed +// up a video too much, it can't keep up, and rendering stops updating except on +// the time bar. For really high speeds, audio becomes a bottleneck and we just +// use up the data we have, which may not achieve the speed requested, but will +// not crash the tab. +// +// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems +// like a busy loop). It gets unresponsive, although its not completely dead. +// +// Also our timers are not very accurate (especially for ogg), which becomes +// evident at low speeds and on Vista. Since other speeds are risky and outside +// the norms, we think 1/16x to 16x is a safe and useful range for now. +const float kMinRate = 0.0625f; +const float kMaxRate = 16.0f; + +} // namespace + +namespace webkit_glue { + +///////////////////////////////////////////////////////////////////////////// +// WebMediaPlayerImpl::Proxy implementation + +WebMediaPlayerImpl::Proxy::Proxy(MessageLoop* render_loop, + WebMediaPlayerImpl* webmediaplayer) + : render_loop_(render_loop), + webmediaplayer_(webmediaplayer), + outstanding_repaints_(0) { + DCHECK(render_loop_); + DCHECK(webmediaplayer_); +} + +WebMediaPlayerImpl::Proxy::~Proxy() { + Detach(); +} + +void WebMediaPlayerImpl::Proxy::Repaint() { + AutoLock auto_lock(lock_); + if (outstanding_repaints_ < kMaxOutstandingRepaints) { + ++outstanding_repaints_; + + render_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &WebMediaPlayerImpl::Proxy::RepaintTask)); + } +} + +void WebMediaPlayerImpl::Proxy::SetVideoRenderer( + WebVideoRenderer* video_renderer) { + video_renderer_ = video_renderer; +} + +void WebMediaPlayerImpl::Proxy::Paint(skia::PlatformCanvas* canvas, + const gfx::Rect& dest_rect) { + DCHECK(MessageLoop::current() == render_loop_); + if (video_renderer_) { + video_renderer_->Paint(canvas, dest_rect); + } +} + +void WebMediaPlayerImpl::Proxy::SetSize(const gfx::Rect& rect) { + DCHECK(MessageLoop::current() == render_loop_); + if (video_renderer_) { + video_renderer_->SetRect(rect); + } +} + +void WebMediaPlayerImpl::Proxy::Detach() { + DCHECK(MessageLoop::current() == render_loop_); + webmediaplayer_ = NULL; + video_renderer_ = NULL; +} + +void WebMediaPlayerImpl::Proxy::PipelineInitializationCallback() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &WebMediaPlayerImpl::Proxy::PipelineInitializationTask)); +} + +void WebMediaPlayerImpl::Proxy::PipelineSeekCallback() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &WebMediaPlayerImpl::Proxy::PipelineSeekTask)); +} + +void WebMediaPlayerImpl::Proxy::PipelineEndedCallback() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &WebMediaPlayerImpl::Proxy::PipelineEndedTask)); +} + +void WebMediaPlayerImpl::Proxy::PipelineErrorCallback() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &WebMediaPlayerImpl::Proxy::PipelineErrorTask)); +} + +void WebMediaPlayerImpl::Proxy::NetworkEventCallback() { + render_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &WebMediaPlayerImpl::Proxy::NetworkEventTask)); +} + +void WebMediaPlayerImpl::Proxy::RepaintTask() { + DCHECK(MessageLoop::current() == render_loop_); + { + AutoLock auto_lock(lock_); + --outstanding_repaints_; + DCHECK_GE(outstanding_repaints_, 0); + } + if (webmediaplayer_) { + webmediaplayer_->Repaint(); + } +} + +void WebMediaPlayerImpl::Proxy::PipelineInitializationTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) { + webmediaplayer_->OnPipelineInitialize(); + } +} + +void WebMediaPlayerImpl::Proxy::PipelineSeekTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) { + webmediaplayer_->OnPipelineSeek(); + } +} + +void WebMediaPlayerImpl::Proxy::PipelineEndedTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) { + webmediaplayer_->OnPipelineEnded(); + } +} + +void WebMediaPlayerImpl::Proxy::PipelineErrorTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) { + webmediaplayer_->OnPipelineError(); + } +} + +void WebMediaPlayerImpl::Proxy::NetworkEventTask() { + DCHECK(MessageLoop::current() == render_loop_); + if (webmediaplayer_) { + webmediaplayer_->OnNetworkEvent(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// WebMediaPlayerImpl implementation + +WebMediaPlayerImpl::WebMediaPlayerImpl(WebKit::WebMediaPlayerClient* client, + media::FilterFactoryCollection* factory, + WebVideoRendererFactoryFactory* + video_renderer_factory) + : network_state_(WebKit::WebMediaPlayer::Empty), + ready_state_(WebKit::WebMediaPlayer::HaveNothing), + main_loop_(NULL), + filter_factory_(factory), + pipeline_thread_("PipelineThread"), + paused_(true), + playback_rate_(0.0f), + client_(client), + pipeline_stopped_(false, false) { + // Saves the current message loop. + DCHECK(!main_loop_); + main_loop_ = MessageLoop::current(); + + // Make sure this gets deleted. + scoped_ptr<WebVideoRendererFactoryFactory> + scoped_video_renderer_factory(video_renderer_factory); + + // Create the pipeline and its thread. + if (!pipeline_thread_.Start()) { + NOTREACHED() << "Could not start PipelineThread"; + return; + } + + pipeline_ = new media::PipelineImpl(pipeline_thread_.message_loop()); + + // Also we want to be notified of |main_loop_| destruction. + main_loop_->AddDestructionObserver(this); + + // Creates the proxy. + proxy_ = new Proxy(main_loop_, this); + + // Set our pipeline callbacks. + pipeline_->SetPipelineEndedCallback(NewCallback(proxy_.get(), + &WebMediaPlayerImpl::Proxy::PipelineEndedCallback)); + pipeline_->SetPipelineErrorCallback(NewCallback(proxy_.get(), + &WebMediaPlayerImpl::Proxy::PipelineErrorCallback)); + pipeline_->SetNetworkEventCallback(NewCallback(proxy_.get(), + &WebMediaPlayerImpl::Proxy::NetworkEventCallback)); + + // Add in the default filter factories. + filter_factory_->AddFactory(media::FFmpegDemuxer::CreateFilterFactory()); + filter_factory_->AddFactory(media::FFmpegAudioDecoder::CreateFactory()); + filter_factory_->AddFactory(media::FFmpegVideoDecoder::CreateFactory()); + filter_factory_->AddFactory(media::NullAudioRenderer::CreateFilterFactory()); + filter_factory_->AddFactory(video_renderer_factory->CreateFactory(proxy_)); +} + +WebMediaPlayerImpl::~WebMediaPlayerImpl() { + Destroy(); + + // Finally tell the |main_loop_| we don't want to be notified of destruction + // event. + if (main_loop_) { + main_loop_->RemoveDestructionObserver(this); + } +} + +void WebMediaPlayerImpl::load(const WebKit::WebURL& url) { + DCHECK(MessageLoop::current() == main_loop_); + DCHECK(proxy_); + + // Handle any volume changes that occured before load(). + setVolume(GetClient()->volume()); + + // Initialize the pipeline. + SetNetworkState(WebKit::WebMediaPlayer::Loading); + SetReadyState(WebKit::WebMediaPlayer::HaveNothing); + pipeline_->Start( + filter_factory_.get(), + url.spec(), + NewCallback(proxy_.get(), + &WebMediaPlayerImpl::Proxy::PipelineInitializationCallback)); +} + +void WebMediaPlayerImpl::cancelLoad() { + DCHECK(MessageLoop::current() == main_loop_); +} + +void WebMediaPlayerImpl::play() { + DCHECK(MessageLoop::current() == main_loop_); + + paused_ = false; + pipeline_->SetPlaybackRate(playback_rate_); +} + +void WebMediaPlayerImpl::pause() { + DCHECK(MessageLoop::current() == main_loop_); + + paused_ = true; + pipeline_->SetPlaybackRate(0.0f); + paused_time_ = pipeline_->GetCurrentTime(); +} + +bool WebMediaPlayerImpl::supportsFullscreen() const { + DCHECK(MessageLoop::current() == main_loop_); + return true; +} + +bool WebMediaPlayerImpl::supportsSave() const { + DCHECK(MessageLoop::current() == main_loop_); + return true; +} + +void WebMediaPlayerImpl::seek(float seconds) { + DCHECK(MessageLoop::current() == main_loop_); + + // WebKit fires a seek(0) at the very start, however pipeline already does a + // seek(0) internally. Avoid doing seek(0) the second time because this will + // cause extra pre-rolling and will break servers without range request + // support. + // + // We still have to notify WebKit that time has changed otherwise + // HTMLMediaElement gets into an inconsistent state. + if (pipeline_->GetCurrentTime().ToInternalValue() == 0 && seconds == 0) { + GetClient()->timeChanged(); + return; + } + + // Drop our ready state if the media file isn't fully loaded. + if (!pipeline_->IsLoaded()) { + SetReadyState(WebKit::WebMediaPlayer::HaveMetadata); + } + + // Try to preserve as much accuracy as possible. + float microseconds = seconds * base::Time::kMicrosecondsPerSecond; + base::TimeDelta seek_time = + base::TimeDelta::FromMicroseconds(static_cast<int64>(microseconds)); + + // Update our paused time. + if (paused_) { + paused_time_ = seek_time; + } + + // Kick off the asynchronous seek! + pipeline_->Seek( + seek_time, + NewCallback(proxy_.get(), + &WebMediaPlayerImpl::Proxy::PipelineSeekCallback)); +} + +void WebMediaPlayerImpl::setEndTime(float seconds) { + DCHECK(MessageLoop::current() == main_loop_); + + // TODO(hclam): add method call when it has been implemented. + return; +} + +void WebMediaPlayerImpl::setRate(float rate) { + DCHECK(MessageLoop::current() == main_loop_); + + // TODO(kylep): Remove when support for negatives is added. Also, modify the + // following checks so rewind uses reasonable values also. + if (rate < 0.0f) + return; + + // Limit rates to reasonable values by clamping. + if (rate != 0.0f) { + if (rate < kMinRate) + rate = kMinRate; + else if (rate > kMaxRate) + rate = kMaxRate; + } + + playback_rate_ = rate; + if (!paused_) { + pipeline_->SetPlaybackRate(rate); + } +} + +void WebMediaPlayerImpl::setVolume(float volume) { + DCHECK(MessageLoop::current() == main_loop_); + + pipeline_->SetVolume(volume); +} + +void WebMediaPlayerImpl::setVisible(bool visible) { + DCHECK(MessageLoop::current() == main_loop_); + + // TODO(hclam): add appropriate method call when pipeline has it implemented. + return; +} + +bool WebMediaPlayerImpl::setAutoBuffer(bool autoBuffer) { + DCHECK(MessageLoop::current() == main_loop_); + + return false; +} + +bool WebMediaPlayerImpl::totalBytesKnown() { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->GetTotalBytes() != 0; +} + +bool WebMediaPlayerImpl::hasVideo() const { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->IsRendered(media::mime_type::kMajorTypeVideo); +} + +bool WebMediaPlayerImpl::hasAudio() const { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->IsRendered(media::mime_type::kMajorTypeAudio); +} + +WebKit::WebSize WebMediaPlayerImpl::naturalSize() const { + DCHECK(MessageLoop::current() == main_loop_); + + size_t width, height; + pipeline_->GetVideoSize(&width, &height); + return WebKit::WebSize(width, height); +} + +bool WebMediaPlayerImpl::paused() const { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->GetPlaybackRate() == 0.0f; +} + +bool WebMediaPlayerImpl::seeking() const { + DCHECK(MessageLoop::current() == main_loop_); + + if (ready_state_ == WebKit::WebMediaPlayer::HaveNothing) + return false; + + return ready_state_ == WebKit::WebMediaPlayer::HaveMetadata; +} + +float WebMediaPlayerImpl::duration() const { + DCHECK(MessageLoop::current() == main_loop_); + + return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF()); +} + +float WebMediaPlayerImpl::currentTime() const { + DCHECK(MessageLoop::current() == main_loop_); + + if (paused_) { + return static_cast<float>(paused_time_.InSecondsF()); + } + return static_cast<float>(pipeline_->GetCurrentTime().InSecondsF()); +} + +int WebMediaPlayerImpl::dataRate() const { + DCHECK(MessageLoop::current() == main_loop_); + + // TODO(hclam): Add this method call if pipeline has it in the interface. + return 0; +} + +const WebKit::WebTimeRanges& WebMediaPlayerImpl::buffered() { + DCHECK(MessageLoop::current() == main_loop_); + + // Update buffered_ with the most recent buffered time. + if (buffered_.size() > 0) { + float buffered_time = static_cast<float>( + pipeline_->GetBufferedTime().InSecondsF()); + if (buffered_time >= buffered_[0].start) + buffered_[0].end = buffered_time; + } + + return buffered_; +} + +float WebMediaPlayerImpl::maxTimeSeekable() const { + DCHECK(MessageLoop::current() == main_loop_); + + // If we are performing streaming, we report that we cannot seek at all. + // We are using this flag to indicate if the data source supports seeking + // or not. We should be able to seek even if we are performing streaming. + // TODO(hclam): We need to update this when we have better caching. + if (pipeline_->IsStreaming()) + return 0.0f; + return static_cast<float>(pipeline_->GetMediaDuration().InSecondsF()); +} + +unsigned long long WebMediaPlayerImpl::bytesLoaded() const { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->GetBufferedBytes(); +} + +unsigned long long WebMediaPlayerImpl::totalBytes() const { + DCHECK(MessageLoop::current() == main_loop_); + + return pipeline_->GetTotalBytes(); +} + +void WebMediaPlayerImpl::setSize(const WebSize& size) { + DCHECK(MessageLoop::current() == main_loop_); + DCHECK(proxy_); + + proxy_->SetSize(gfx::Rect(0, 0, size.width, size.height)); +} + +void WebMediaPlayerImpl::paint(WebCanvas* canvas, + const WebRect& rect) { + DCHECK(MessageLoop::current() == main_loop_); + DCHECK(proxy_); + +#if WEBKIT_USING_SKIA + proxy_->Paint(canvas, rect); +#elif WEBKIT_USING_CG + // Get the current scaling in X and Y. + CGAffineTransform mat = CGContextGetCTM(canvas); + float scale_x = sqrt(mat.a * mat.a + mat.b * mat.b); + float scale_y = sqrt(mat.c * mat.c + mat.d * mat.d); + float inverse_scale_x = SkScalarNearlyZero(scale_x) ? 0.0f : 1.0f / scale_x; + float inverse_scale_y = SkScalarNearlyZero(scale_y) ? 0.0f : 1.0f / scale_y; + int scaled_width = static_cast<int>(rect.width * fabs(scale_x)); + int scaled_height = static_cast<int>(rect.height * fabs(scale_y)); + + // Make sure we don't create a huge canvas. + // TODO(hclam): Respect the aspect ratio. + if (scaled_width > static_cast<int>(media::Limits::kMaxCanvas)) + scaled_width = media::Limits::kMaxCanvas; + if (scaled_height > static_cast<int>(media::Limits::kMaxCanvas)) + scaled_height = media::Limits::kMaxCanvas; + + // If there is no preexisting platform canvas, or if the size has + // changed, recreate the canvas. This is to avoid recreating the bitmap + // buffer over and over for each frame of video. + if (!skia_canvas_.get() || + skia_canvas_->getDevice()->width() != scaled_width || + skia_canvas_->getDevice()->height() != scaled_height) { + skia_canvas_.reset( + new skia::PlatformCanvas(scaled_width, scaled_height, true)); + } + + // Draw to our temporary skia canvas. + gfx::Rect normalized_rect(scaled_width, scaled_height); + proxy_->Paint(skia_canvas_.get(), normalized_rect); + + // The mac coordinate system is flipped vertical from the normal skia + // coordinates. During painting of the frame, flip the coordinates + // system and, for simplicity, also translate the clip rectangle to + // start at 0,0. + CGContextSaveGState(canvas); + CGContextTranslateCTM(canvas, rect.x, rect.height + rect.y); + CGContextScaleCTM(canvas, inverse_scale_x, -inverse_scale_y); + + // We need a local variable CGRect version for DrawToContext. + CGRect normalized_cgrect = + CGRectMake(normalized_rect.x(), normalized_rect.y(), + normalized_rect.width(), normalized_rect.height()); + + // Copy the frame rendered to our temporary skia canvas onto the passed in + // canvas. + skia_canvas_->getTopPlatformDevice().DrawToContext(canvas, 0, 0, + &normalized_cgrect); + + CGContextRestoreGState(canvas); +#else + NOTIMPLEMENTED() << "We only support rendering to skia or CG"; +#endif +} + +bool WebMediaPlayerImpl::hasSingleSecurityOrigin() const { + // TODO(scherkus): we'll need to do something smarter here if/when we start to + // support formats that contain references to external resources (i.e., MP4s + // containing links to other MP4s). See http://crbug.com/25432 + return true; +} + +WebKit::WebMediaPlayer::MovieLoadType + WebMediaPlayerImpl::movieLoadType() const { + DCHECK(MessageLoop::current() == main_loop_); + + // TODO(hclam): If the pipeline is performing streaming, we say that this is + // a live stream. But instead it should be a StoredStream if we have proper + // caching. + if (pipeline_->IsStreaming()) + return WebKit::WebMediaPlayer::LiveStream; + return WebKit::WebMediaPlayer::Unknown; +} + +void WebMediaPlayerImpl::WillDestroyCurrentMessageLoop() { + Destroy(); + main_loop_ = NULL; +} + +void WebMediaPlayerImpl::Repaint() { + DCHECK(MessageLoop::current() == main_loop_); + GetClient()->repaint(); +} + +void WebMediaPlayerImpl::OnPipelineInitialize() { + DCHECK(MessageLoop::current() == main_loop_); + if (pipeline_->GetError() == media::PIPELINE_OK) { + // Only keep one time range starting from 0. + WebKit::WebTimeRanges new_buffered(static_cast<size_t>(1)); + new_buffered[0].start = 0.0f; + new_buffered[0].end = + static_cast<float>(pipeline_->GetMediaDuration().InSecondsF()); + buffered_.swap(new_buffered); + + // Since we have initialized the pipeline, say we have everything otherwise + // we'll remain either loading/idle. + // TODO(hclam): change this to report the correct status. + SetReadyState(WebKit::WebMediaPlayer::HaveMetadata); + SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData); + if (pipeline_->IsLoaded()) { + SetNetworkState(WebKit::WebMediaPlayer::Loaded); + } + } else { + // TODO(hclam): should use pipeline_->GetError() to determine the state + // properly and reports error using MediaError. + // WebKit uses FormatError to indicate an error for bogus URL or bad file. + // Since we are at the initialization stage we can safely treat every error + // as format error. Should post a task to call to |webmediaplayer_|. + SetNetworkState(WebKit::WebMediaPlayer::FormatError); + } + + // Repaint to trigger UI update. + Repaint(); +} + +void WebMediaPlayerImpl::OnPipelineSeek() { + DCHECK(MessageLoop::current() == main_loop_); + if (pipeline_->GetError() == media::PIPELINE_OK) { + // Update our paused time. + if (paused_) { + paused_time_ = pipeline_->GetCurrentTime(); + } + + SetReadyState(WebKit::WebMediaPlayer::HaveEnoughData); + GetClient()->timeChanged(); + } +} + +void WebMediaPlayerImpl::OnPipelineEnded() { + DCHECK(MessageLoop::current() == main_loop_); + if (pipeline_->GetError() == media::PIPELINE_OK) { + GetClient()->timeChanged(); + } +} + +void WebMediaPlayerImpl::OnPipelineError() { + DCHECK(MessageLoop::current() == main_loop_); + switch (pipeline_->GetError()) { + case media::PIPELINE_OK: + case media::PIPELINE_ERROR_INITIALIZATION_FAILED: + case media::PIPELINE_ERROR_REQUIRED_FILTER_MISSING: + case media::PIPELINE_ERROR_COULD_NOT_RENDER: + case media::PIPELINE_ERROR_URL_NOT_FOUND: + case media::PIPELINE_ERROR_NETWORK: + case media::PIPELINE_ERROR_READ: + case media::DEMUXER_ERROR_COULD_NOT_OPEN: + case media::DEMUXER_ERROR_COULD_NOT_PARSE: + case media::DEMUXER_ERROR_NO_SUPPORTED_STREAMS: + case media::DEMUXER_ERROR_COULD_NOT_CREATE_THREAD: + // Format error. + SetNetworkState(WebMediaPlayer::FormatError); + break; + + case media::PIPELINE_ERROR_DECODE: + case media::PIPELINE_ERROR_ABORT: + case media::PIPELINE_ERROR_OUT_OF_MEMORY: + case media::PIPELINE_ERROR_AUDIO_HARDWARE: + // Decode error. + SetNetworkState(WebMediaPlayer::DecodeError); + break; + } + + // Repaint to trigger UI update. + Repaint(); +} + +void WebMediaPlayerImpl::OnNetworkEvent() { + DCHECK(MessageLoop::current() == main_loop_); + if (pipeline_->GetError() == media::PIPELINE_OK) { + if (pipeline_->IsNetworkActive()) + SetNetworkState(WebKit::WebMediaPlayer::Loading); + else + SetNetworkState(WebKit::WebMediaPlayer::Idle); + } +} + +void WebMediaPlayerImpl::SetNetworkState( + WebKit::WebMediaPlayer::NetworkState state) { + DCHECK(MessageLoop::current() == main_loop_); + // Always notify to ensure client has the latest value. + network_state_ = state; + GetClient()->networkStateChanged(); +} + +void WebMediaPlayerImpl::SetReadyState( + WebKit::WebMediaPlayer::ReadyState state) { + DCHECK(MessageLoop::current() == main_loop_); + // Always notify to ensure client has the latest value. + ready_state_ = state; + GetClient()->readyStateChanged(); +} + +void WebMediaPlayerImpl::Destroy() { + DCHECK(MessageLoop::current() == main_loop_); + + // Make sure to kill the pipeline so there's no more media threads running. + // Note: stopping the pipeline might block for a long time. + pipeline_->Stop(NewCallback(this, + &WebMediaPlayerImpl::PipelineStoppedCallback)); + pipeline_stopped_.Wait(); + pipeline_thread_.Stop(); + + // And then detach the proxy, it may live on the render thread for a little + // longer until all the tasks are finished. + if (proxy_) { + proxy_->Detach(); + proxy_ = NULL; + } +} + +void WebMediaPlayerImpl::PipelineStoppedCallback() { + pipeline_stopped_.Signal(); +} + +WebKit::WebMediaPlayerClient* WebMediaPlayerImpl::GetClient() { + DCHECK(MessageLoop::current() == main_loop_); + DCHECK(client_); + return client_; +} + +} // namespace webkit_glue diff --git a/webkit/glue/webmediaplayer_impl.h b/webkit/glue/webmediaplayer_impl.h new file mode 100644 index 0000000..e8794dc --- /dev/null +++ b/webkit/glue/webmediaplayer_impl.h @@ -0,0 +1,340 @@ +// Copyright (c) 2010 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. + +// Delegate calls from WebCore::MediaPlayerPrivate to Chrome's video player. +// It contains PipelineImpl which is the actual media player pipeline, it glues +// the media player pipeline, data source, audio renderer and renderer. +// PipelineImpl would creates multiple threads and access some public methods +// of this class, so we need to be extra careful about concurrent access of +// methods and members. +// +// WebMediaPlayerImpl works with multiple objects, the most important ones are: +// +// media::PipelineImpl +// The media playback pipeline. +// +// WebVideoRenderer +// Video renderer object. +// +// WebMediaPlayerImpl::Proxy +// Proxies methods calls from the media pipeline to WebKit. +// +// WebKit::WebMediaPlayerClient +// WebKit client of this media player object. +// +// The following diagram shows the relationship of these objects: +// (note: ref-counted reference is marked by a "r".) +// +// WebMediaPlayerImpl ------> PipelineImpl +// | ^ | r +// | | v +// | | WebVideoRenderer +// | | ^ r +// | | | +// | r | r | +// .------> Proxy <-----. +// | +// | +// v +// WebMediaPlayerClient +// +// Notice that Proxy and WebVideoRenderer are referencing each other. This +// interdependency has to be treated carefully. +// +// Other issues: +// During tear down of the whole browser or a tab, the DOM tree may not be +// destructed nicely, and there will be some dangling media threads trying to +// the main thread, so we need this class to listen to destruction event of the +// main thread and cleanup the media threads when the even is received. Also +// at destruction of this class we will need to unhook it from destruction event +// list of the main thread. + +#ifndef WEBKIT_GLUE_WEBMEDIAPLAYER_IMPL_H_ +#define WEBKIT_GLUE_WEBMEDIAPLAYER_IMPL_H_ + +#include <vector> + +#include "base/lock.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/waitable_event.h" +#include "gfx/rect.h" +#include "gfx/size.h" +#include "media/base/filters.h" +#include "media/base/pipeline_impl.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/WebKit/WebKit/chromium/public/WebMediaPlayer.h" +#include "third_party/WebKit/WebKit/chromium/public/WebMediaPlayerClient.h" + +class GURL; + +namespace media { +class FilterFactoryCollection; +} + +namespace webkit_glue { + +class WebVideoRenderer; +class WebVideoRendererFactoryFactory; + +class WebMediaPlayerImpl : public WebKit::WebMediaPlayer, + public MessageLoop::DestructionObserver { + public: + // A proxy class that dispatches method calls from the media pipeline to + // WebKit. Since there are multiple threads in the media pipeline and there's + // need for the media pipeline to call to WebKit, e.g. repaint requests, + // initialization events, etc, we have this class to bridge all method calls + // from the media pipeline on different threads and serialize these calls + // on the render thread. + // Because of the nature of this object that it works with different threads, + // it is made ref-counted. + class Proxy : public base::RefCountedThreadSafe<Proxy> { + public: + Proxy(MessageLoop* render_loop, + WebMediaPlayerImpl* webmediaplayer); + + // Public methods called from the video renderer. + void Repaint(); + void SetVideoRenderer(WebVideoRenderer* video_renderer); + + // Public methods called from WebMediaPlayerImpl. + void Paint(skia::PlatformCanvas* canvas, const gfx::Rect& dest_rect); + void SetSize(const gfx::Rect& rect); + void Detach(); + + // Public methods called from the pipeline via callback issued by + // WebMediaPlayerImpl. + void PipelineInitializationCallback(); + void PipelineSeekCallback(); + void PipelineEndedCallback(); + void PipelineErrorCallback(); + void NetworkEventCallback(); + + // Returns the message loop used by the proxy. + MessageLoop* message_loop() { return render_loop_; } + + private: + friend class base::RefCountedThreadSafe<Proxy>; + + virtual ~Proxy(); + + // Invoke |webmediaplayer_| to perform a repaint. + void RepaintTask(); + + // Notify |webmediaplayer_| that initialization has finished. + void PipelineInitializationTask(); + + // Notify |webmediaplayer_| that a seek has finished. + void PipelineSeekTask(); + + // Notify |webmediaplayer_| that the media has ended. + void PipelineEndedTask(); + + // Notify |webmediaplayer_| that a pipeline error has been set. + void PipelineErrorTask(); + + // Notify |webmediaplayer_| that there's a network event. + void NetworkEventTask(); + + // The render message loop where WebKit lives. + MessageLoop* render_loop_; + WebMediaPlayerImpl* webmediaplayer_; + scoped_refptr<WebVideoRenderer> video_renderer_; + + Lock lock_; + int outstanding_repaints_; + }; + + // Construct a WebMediaPlayerImpl with reference to the client, and media + // filter factory collection. By providing the filter factory collection + // the implementor can provide more specific media filters that does resource + // loading and rendering. |factory| should contain filter factories for: + // 1. Data source + // 2. Audio renderer + // 3. Video renderer (optional) + // + // There are some default filters provided by this method: + // 1. FFmpeg demuxer + // 2. FFmpeg audio decoder + // 3. FFmpeg video decoder + // 4. Video renderer + // 5. Null audio renderer + // The video renderer provided by this class is using the graphics context + // provided by WebKit to perform renderering. The simple data source does + // resource loading by loading the whole resource object into memory. Null + // audio renderer is a fake audio device that plays silence. Provider of the + // |factory| can override the default filters by adding extra filters to + // |factory| before calling this method. + // + // |video_renderer_factory| is used to construct a factory that should create + // a subclass of WebVideoRenderer. Is deleted by WebMediaPlayerImpl. + WebMediaPlayerImpl(WebKit::WebMediaPlayerClient* client, + media::FilterFactoryCollection* factory, + WebVideoRendererFactoryFactory* video_renderer_factory); + virtual ~WebMediaPlayerImpl(); + + virtual void load(const WebKit::WebURL& url); + virtual void cancelLoad(); + + // Playback controls. + virtual void play(); + virtual void pause(); + virtual bool supportsFullscreen() const; + virtual bool supportsSave() const; + virtual void seek(float seconds); + virtual void setEndTime(float seconds); + virtual void setRate(float rate); + virtual void setVolume(float volume); + virtual void setVisible(bool visible); + virtual bool setAutoBuffer(bool autoBuffer); + virtual bool totalBytesKnown(); + virtual const WebKit::WebTimeRanges& buffered(); + virtual float maxTimeSeekable() const; + + // Methods for painting. + virtual void setSize(const WebKit::WebSize& size); + + virtual void paint(WebKit::WebCanvas* canvas, const WebKit::WebRect& rect); + + // True if the loaded media has a playable video/audio track. + virtual bool hasVideo() const; + virtual bool hasAudio() const; + + // Dimensions of the video. + virtual WebKit::WebSize naturalSize() const; + + // Getters of playback state. + virtual bool paused() const; + virtual bool seeking() const; + virtual float duration() const; + virtual float currentTime() const; + + // Get rate of loading the resource. + virtual int32 dataRate() const; + + // Internal states of loading and network. + // TODO(hclam): Ask the pipeline about the state rather than having reading + // them from members which would cause race conditions. + virtual WebKit::WebMediaPlayer::NetworkState networkState() const { + return network_state_; + } + virtual WebKit::WebMediaPlayer::ReadyState readyState() const { + return ready_state_; + } + + virtual unsigned long long bytesLoaded() const; + virtual unsigned long long totalBytes() const; + + virtual bool hasSingleSecurityOrigin() const; + virtual WebKit::WebMediaPlayer::MovieLoadType movieLoadType() const; + + // As we are closing the tab or even the browser, |main_loop_| is destroyed + // even before this object gets destructed, so we need to know when + // |main_loop_| is being destroyed and we can stop posting repaint task + // to it. + virtual void WillDestroyCurrentMessageLoop(); + + void Repaint(); + + void OnPipelineInitialize(); + + void OnPipelineSeek(); + + void OnPipelineEnded(); + + void OnPipelineError(); + + void OnNetworkEvent(); + + private: + // Helpers that set the network/ready state and notifies the client if + // they've changed. + void SetNetworkState(WebKit::WebMediaPlayer::NetworkState state); + void SetReadyState(WebKit::WebMediaPlayer::ReadyState state); + + // Destroy resources held. + void Destroy(); + + // Callback executed after |pipeline_| stops which signals Destroy() + // to continue. + void PipelineStoppedCallback(); + + // Getter method to |client_|. + WebKit::WebMediaPlayerClient* GetClient(); + + // TODO(hclam): get rid of these members and read from the pipeline directly. + WebKit::WebMediaPlayer::NetworkState network_state_; + WebKit::WebMediaPlayer::ReadyState ready_state_; + + // Keep a list of buffered time ranges. + WebKit::WebTimeRanges buffered_; + + // Message loops for posting tasks between Chrome's main thread. Also used + // for DCHECKs so methods calls won't execute in the wrong thread. + MessageLoop* main_loop_; + + // A collection of factories for creating filters. + scoped_refptr<media::FilterFactoryCollection> filter_factory_; + + // The actual pipeline and the thread it runs on. + scoped_refptr<media::PipelineImpl> pipeline_; + base::Thread pipeline_thread_; + + // Playback state. + // + // TODO(scherkus): we have these because Pipeline favours the simplicity of a + // single "playback rate" over worrying about paused/stopped etc... It forces + // all clients to manage the pause+playback rate externally, but is that + // really a bad thing? + // + // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't want + // to hang the render thread during pause(), we record the time at the same + // time we pause and then return that value in currentTime(). Otherwise our + // clock can creep forward a little bit while the asynchronous + // SetPlaybackRate(0) is being executed. + bool paused_; + float playback_rate_; + base::TimeDelta paused_time_; + + WebKit::WebMediaPlayerClient* client_; + + scoped_refptr<Proxy> proxy_; + + // Used to block Destroy() until Pipeline::Stop() is completed. + base::WaitableEvent pipeline_stopped_; + +#if WEBKIT_USING_CG + scoped_ptr<skia::PlatformCanvas> skia_canvas_; +#endif + + DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerImpl); +}; + +// TODO(scherkus): WebMediaPlayerImpl creates and injects its Proxy into a +// video renderer factory, so we need to (unfortunately) have a factory of a +// factory so we can receive the proxy pointer without violating the +// separation of renderer code from webkit glue code. This is part of a +// longer-term plan to rethink our FilterFactory strategy (refer to +// http://crbug.com/28207). +// +// Either that or we rethink this Proxy business as a short-term solution. +class WebVideoRendererFactoryFactory { + public: + WebVideoRendererFactoryFactory() {} + virtual ~WebVideoRendererFactoryFactory() {} + + // Creates a FilterFactory which should be capable of creating a + // WebVideoRenderer subclass. + virtual media::FilterFactory* CreateFactory( + WebMediaPlayerImpl::Proxy* proxy) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(WebVideoRendererFactoryFactory); +}; + + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBMEDIAPLAYER_IMPL_H_ diff --git a/webkit/glue/webmenuitem.h b/webkit/glue/webmenuitem.h new file mode 100644 index 0000000..4b0fdc3 --- /dev/null +++ b/webkit/glue/webmenuitem.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBMENUITEM_H_ +#define WEBMENUITEM_H_ + +#include "base/string16.h" +#include "third_party/WebKit/WebKit/chromium/public/WebMenuItemInfo.h" + +// Container for information about entries in an HTML select popup menu and +// custom entries of the context menu. +struct WebMenuItem { + enum Type { + OPTION = WebKit::WebMenuItemInfo::Option, + CHECKABLE_OPTION = WebKit::WebMenuItemInfo::CheckableOption, + GROUP = WebKit::WebMenuItemInfo::Group, + SEPARATOR = WebKit::WebMenuItemInfo::Separator + }; + + string16 label; + Type type; + unsigned action; + bool enabled; + bool checked; + + WebMenuItem() : type(OPTION), action(0), enabled(false), checked(false) { + } + + WebMenuItem(const WebKit::WebMenuItemInfo& item) + : label(item.label), + type(static_cast<Type>(item.type)), + action(item.action), + enabled(item.enabled), + checked(item.checked) { + } +}; + +#endif // WEBMENUITEM_H_ diff --git a/webkit/glue/webmenurunner_mac.h b/webkit/glue/webmenurunner_mac.h new file mode 100644 index 0000000..c56eeeb --- /dev/null +++ b/webkit/glue/webmenurunner_mac.h @@ -0,0 +1,70 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBMENURUNNER_MAC_H_ +#define WEBKIT_GLUE_WEBMENURUNNER_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include <vector> + +#include "base/scoped_nsobject.h" +#include "webkit/glue/webmenuitem.h" + + +// WebMenuRunner --------------------------------------------------------------- +// A class for determining whether an item was selected from an HTML select +// control, or if the menu was dismissed without making a selection. If a menu +// item is selected, MenuDelegate is informed and sets a flag which can be +// queried after the menu has finished running. + +@interface WebMenuRunner : NSObject { + @private + // The native menu control. + scoped_nsobject<NSMenu> menu_; + + // A flag set to YES if a menu item was chosen, or NO if the menu was + // dismissed without selecting an item. + BOOL menuItemWasChosen_; + + // The index of the selected menu item. + int index_; + + // The font size being used for the menu. + CGFloat fontSize_; +} + +// Initializes the MenuDelegate with a list of items sent from WebKit. +- (id)initWithItems:(const std::vector<WebMenuItem>&)items + fontSize:(CGFloat)fontSize + rightAligned:(BOOL)rightAligned; + +// Returns YES if an item was selected from the menu, NO if the menu was +// dismissed. +- (BOOL)menuItemWasChosen; + +// Displays and runs a native popup menu. +- (void)runMenuInView:(NSView*)view + withBounds:(NSRect)bounds + initialIndex:(int)index; + +// Returns the index of selected menu item, or its initial value (-1) if no item +// was selected. +- (int)indexOfSelectedItem; + +@end // @interface WebMenuRunner + +namespace webkit_glue { +// Helper function for users of WebMenuRunner, for manufacturing input events to +// send to WebKit. If |item_chosen| is YES, we manufacture a mouse click event +// that corresponds to the menu item that was selected, |selected_index|, based +// on the position of the mouse click. Of |item_chosen| is NO, we create a +// keyboard event that simulates an ESC (menu dismissal) action. The event is +// designed to be sent to WebKit for processing by the PopupMenu class. +NSEvent* EventWithMenuAction(BOOL item_chosen, int window_num, + int item_height, int selected_index, + NSRect menu_bounds, NSRect view_bounds); +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBMENURUNNER_MAC_H_ diff --git a/webkit/glue/webmenurunner_mac.mm b/webkit/glue/webmenurunner_mac.mm new file mode 100644 index 0000000..e4c8a4b --- /dev/null +++ b/webkit/glue/webmenurunner_mac.mm @@ -0,0 +1,204 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/webmenurunner_mac.h" + +#include "base/sys_string_conversions.h" + +namespace { + +const CGFloat kPopupXOffset = -10.0f; +BOOL gNewNSMenuAPI; + +} // namespace + +#if !defined(MAC_OS_X_VERSION_10_6) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +@interface NSMenu (SnowLeopardSDKDeclarations) +- (BOOL)popUpMenuPositioningItem:(NSMenuItem *)item + atLocation:(NSPoint)location + inView:(NSView *)view; +- (void)setFont:(NSFont *)font; +@end +#endif + +@interface WebMenuRunner (PrivateAPI) + +// Worker function used during initialization. +- (void)addItem:(const WebMenuItem&)item + withAttributes:(NSDictionary*)attrs; + +// A callback for the menu controller object to call when an item is selected +// from the menu. This is not called if the menu is dismissed without a +// selection. +- (void)menuItemSelected:(id)sender; + +@end // WebMenuRunner (PrivateAPI) + +@implementation WebMenuRunner + +- (id)initWithItems:(const std::vector<WebMenuItem>&)items + fontSize:(CGFloat)fontSize + rightAligned:(BOOL)rightAligned { + static BOOL newNSMenuAPIInitialized = NO; + if (!newNSMenuAPIInitialized) { + newNSMenuAPIInitialized = YES; + gNewNSMenuAPI = [NSMenu instancesRespondToSelector: + @selector(popUpMenuPositioningItem:atLocation:inView:)] && + [NSMenu instancesRespondToSelector:@selector(setFont:)]; + } + + if ((self = [super init])) { + menu_.reset([[NSMenu alloc] initWithTitle:@""]); + if (gNewNSMenuAPI) + [menu_ setFont:[NSFont menuFontOfSize:fontSize]]; + [menu_ setAutoenablesItems:NO]; + index_ = -1; + fontSize_ = fontSize; + scoped_nsobject<NSDictionary> attrs; + if (rightAligned) { + // NB: Right-aligning menu items in this manner is known to not work in + // Mac OS X 10.5. + scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( + [[NSMutableParagraphStyle alloc] init]); + [paragraphStyle setAlignment:NSRightTextAlignment]; + attrs.reset([[NSDictionary alloc] initWithObjectsAndKeys: + paragraphStyle, NSParagraphStyleAttributeName, nil]); + } + for (size_t i = 0; i < items.size(); ++i) + [self addItem:items[i] withAttributes:attrs]; + } + return self; +} + +- (void)addItem:(const WebMenuItem&)item + withAttributes:(NSDictionary*)attrs { + if (item.type == WebMenuItem::SEPARATOR) { + [menu_ addItem:[NSMenuItem separatorItem]]; + return; + } + + NSString* title = base::SysUTF16ToNSString(item.label); + NSMenuItem* menuItem = [menu_ addItemWithTitle:title + action:@selector(menuItemSelected:) + keyEquivalent:@""]; + [menuItem setEnabled:(item.enabled && item.type != WebMenuItem::GROUP)]; + [menuItem setTarget:self]; + if (attrs) { + scoped_nsobject<NSAttributedString> attrTitle( + [[NSAttributedString alloc] initWithString:title + attributes:attrs]); + [menuItem setAttributedTitle:attrTitle]; + } + if (gNewNSMenuAPI) + [menuItem setTag:[menu_ numberOfItems] - 1]; +} + +// Reflects the result of the user's interaction with the popup menu. If NO, the +// menu was dismissed without the user choosing an item, which can happen if the +// user clicked outside the menu region or hit the escape key. If YES, the user +// selected an item from the menu. +- (BOOL)menuItemWasChosen { + return menuItemWasChosen_; +} + +- (void)menuItemSelected:(id)sender { + menuItemWasChosen_ = YES; + if (gNewNSMenuAPI) + index_ = [sender tag]; +} + +- (void)runMenuInView:(NSView*)view + withBounds:(NSRect)bounds + initialIndex:(int)index { + if (gNewNSMenuAPI) { + NSMenuItem* selectedItem = [menu_ itemAtIndex:index]; + [selectedItem setState:NSOnState]; + NSPoint anchor = NSMakePoint(NSMinX(bounds) + kPopupXOffset, + NSMaxY(bounds)); + [menu_ popUpMenuPositioningItem:selectedItem + atLocation:anchor + inView:view]; + } else { + // Set up the button cell, converting to NSView coordinates. The menu is + // positioned such that the currently selected menu item appears over the + // popup button, which is the expected Mac popup menu behavior. + NSPopUpButtonCell* button = [[NSPopUpButtonCell alloc] initTextCell:@"" + pullsDown:NO]; + [button autorelease]; + [button setMenu:menu_]; + [button selectItemAtIndex:index]; + [button setFont:[NSFont menuFontOfSize:fontSize_]]; + + // Display the menu, and set a flag if a menu item was chosen. + [button performClickWithFrame:bounds inView:view]; + + if ([self menuItemWasChosen]) + index_ = [button indexOfSelectedItem]; + } +} + +- (int)indexOfSelectedItem { + return index_; +} + +@end // WebMenuRunner + +namespace webkit_glue { + +// Helper function for manufacturing input events to send to WebKit. +NSEvent* EventWithMenuAction(BOOL item_chosen, int window_num, + int item_height, int selected_index, + NSRect menu_bounds, NSRect view_bounds) { + NSEvent* event = nil; + double event_time = (double)(AbsoluteToDuration(UpTime())) / 1000.0; + + if (item_chosen) { + // Construct a mouse up event to simulate the selection of an appropriate + // menu item. + NSPoint click_pos; + click_pos.x = menu_bounds.size.width / 2; + + // This is going to be hard to calculate since the button is painted by + // WebKit, the menu by Cocoa, and we have to translate the selected_item + // index to a coordinate that WebKit's PopupMenu expects which uses a + // different font *and* expects to draw the menu below the button like we do + // on Windows. + // The WebKit popup menu thinks it will draw just below the button, so + // create the click at the offset based on the selected item's index and + // account for the different coordinate system used by NSView. + int item_offset = selected_index * item_height + item_height / 2; + click_pos.y = view_bounds.size.height - item_offset; + event = [NSEvent mouseEventWithType:NSLeftMouseUp + location:click_pos + modifierFlags:0 + timestamp:event_time + windowNumber:window_num + context:nil + eventNumber:0 + clickCount:1 + pressure:1.0]; + } else { + // Fake an ESC key event (keyCode = 0x1B, from webinputevent_mac.mm) and + // forward that to WebKit. + NSPoint key_pos; + key_pos.x = 0; + key_pos.y = 0; + NSString* escape_str = [NSString stringWithFormat:@"%c", 0x1B]; + event = [NSEvent keyEventWithType:NSKeyDown + location:key_pos + modifierFlags:0 + timestamp:event_time + windowNumber:window_num + context:nil + characters:@"" + charactersIgnoringModifiers:escape_str + isARepeat:NO + keyCode:0x1B]; + } + + return event; +} + +} // namespace webkit_glue diff --git a/webkit/glue/webpasswordautocompletelistener_impl.cc b/webkit/glue/webpasswordautocompletelistener_impl.cc new file mode 100644 index 0000000..6f6e754 --- /dev/null +++ b/webkit/glue/webpasswordautocompletelistener_impl.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2006-2008 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. +// +// This file provides the implementaiton of the password manager's autocomplete +// component. + +#include "webkit/glue/webpasswordautocompletelistener_impl.h" + +#include <vector> + +#include "base/string_util.h" +#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" + +using WebKit::WebFrame; +using WebKit::WebView; + +namespace webkit_glue { + +WebInputElementDelegate::WebInputElementDelegate() { +} + +WebInputElementDelegate::WebInputElementDelegate(const WebInputElement& element) + : element_(element) { +} + +WebInputElementDelegate::~WebInputElementDelegate() { +} + +bool WebInputElementDelegate::IsEditable() const { + return element_.isEnabledFormControl() && !element_.hasAttribute("readonly"); +} + +void WebInputElementDelegate::SetValue(const string16& value) { + element_.setValue(value); +} + +bool WebInputElementDelegate::IsAutofilled() const { + return element_.isAutofilled(); +} + +void WebInputElementDelegate::SetAutofilled(bool autofilled) { + if (element_.isAutofilled() == autofilled) + return; + element_.setAutofilled(autofilled); + // Notify any changeEvent listeners. + element_.dispatchFormControlChangeEvent(); +} + +void WebInputElementDelegate::SetSelectionRange(size_t start, size_t end) { + element_.setSelectionRange(start, end); +} + +void WebInputElementDelegate::RefreshAutofillPopup( + const std::vector<string16>& suggestions) { + WebView* webview = element_.document().frame()->view(); + if (webview) { + std::vector<string16> names; + std::vector<string16> labels; + std::vector<int> unique_ids; + + for (size_t i = 0; i < suggestions.size(); ++i) { + names.push_back(suggestions[i]); + labels.push_back(string16()); + unique_ids.push_back(0); + } + + webview->applyAutoFillSuggestions(element_, names, labels, unique_ids, -1); + } +} + +WebPasswordAutocompleteListenerImpl::WebPasswordAutocompleteListenerImpl( + WebInputElementDelegate* username_delegate, + WebInputElementDelegate* password_delegate, + const PasswordFormFillData& data) + : password_delegate_(password_delegate), + username_delegate_(username_delegate), + data_(data) { +} + +void WebPasswordAutocompleteListenerImpl::didBlurInputElement( + const WebString& user_input) { + // If this listener exists, its because the password manager had more than + // one match for the password form, which implies it had at least one + // [preferred] username/password pair. +// DCHECK(data_.basic_data.values.size() == 2); + + if (!password_delegate_->IsEditable()) + return; + + string16 user_input16 = user_input; + + // If enabled, set the password field to match the current username. + if (data_.basic_data.fields[0].value() == user_input16) { + // Preferred username/login is selected. + password_delegate_->SetValue(data_.basic_data.fields[1].value()); + password_delegate_->SetAutofilled(true); + } else if (data_.additional_logins.find(user_input16) != + data_.additional_logins.end()) { + // One of the extra username/logins is selected. + password_delegate_->SetValue(data_.additional_logins[user_input16]); + password_delegate_->SetAutofilled(true); + } +} + +void WebPasswordAutocompleteListenerImpl::performInlineAutocomplete( + const WebString& user_input, + bool backspace_or_delete_pressed, + bool show_suggestions) { + // If wait_for_username is true, we only autofill the password when the + // username field is blurred (i.e not inline) with a matching username string + // entered. + if (data_.wait_for_username) + return; + + string16 user_input16 = user_input; + + // The input text is being changed, so any autofilled password is now + // outdated. + username_delegate_->SetAutofilled(false); + if (password_delegate_->IsAutofilled()) { + password_delegate_->SetValue(string16()); + password_delegate_->SetAutofilled(false); + } + + if (show_suggestions) + showSuggestionPopup(user_input16); + + if (backspace_or_delete_pressed) + return; // Don't inline autocomplete when the user deleted something. + + // Look for any suitable matches to current field text. + // TODO(timsteele): The preferred login (in basic_data.values) and additional + // logins could be bundled into the same data structure (possibly even as + // WebCore strings) upon construction of the PasswordAutocompleteListenerImpl + // to simplify lookup and save string conversions (see SetValue) on each + // successful call to OnInlineAutocompleteNeeded. + if (TryToMatch(user_input16, + data_.basic_data.fields[0].value(), + data_.basic_data.fields[1].value())) { + return; + } + + // Scan additional logins for a match. + for (PasswordFormFillData::LoginCollection::iterator it = + data_.additional_logins.begin(); + it != data_.additional_logins.end(); + ++it) { + if (TryToMatch(user_input16, it->first, it->second)) + return; + } +} + +bool WebPasswordAutocompleteListenerImpl::showSuggestionPopup( + const WebString& value) { + std::vector<string16> suggestions; + GetSuggestions(value, &suggestions); + if (suggestions.empty()) + return false; + + username_delegate_->RefreshAutofillPopup(suggestions); + return true; +} + +bool WebPasswordAutocompleteListenerImpl::TryToMatch(const string16& input, + const string16& username, + const string16& password) { + if (!StartsWith(username, input, false)) + return false; + + // Input matches the username, fill in required values. + username_delegate_->SetValue(username); + username_delegate_->SetSelectionRange(input.length(), username.length()); + username_delegate_->SetAutofilled(true); + if (password_delegate_->IsEditable()) + password_delegate_->SetValue(password); + password_delegate_->SetAutofilled(true); + return true; +} + +void WebPasswordAutocompleteListenerImpl::GetSuggestions( + const string16& input, std::vector<string16>* suggestions) { + if (StartsWith(data_.basic_data.fields[0].value(), input, false)) + suggestions->push_back(data_.basic_data.fields[0].value()); + + for (PasswordFormFillData::LoginCollection::iterator it = + data_.additional_logins.begin(); + it != data_.additional_logins.end(); + ++it) { + if (StartsWith(it->first, input, false)) + suggestions->push_back(it->first); + } +} + +} // namespace webkit_glue diff --git a/webkit/glue/webpasswordautocompletelistener_impl.h b/webkit/glue/webpasswordautocompletelistener_impl.h new file mode 100644 index 0000000..4fea455 --- /dev/null +++ b/webkit/glue/webpasswordautocompletelistener_impl.h @@ -0,0 +1,86 @@ +// Copyright (c) 2006-2008 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. +// +// A concrete definition of the DOM autocomplete framework defined by +// autocomplete_input_listener.h, for the password manager. + +#ifndef WEBKIT_GLUE_PASSWORDAUTOCOMPLETELISTENER_IMPL_H_ +#define WEBKIT_GLUE_PASSWORDAUTOCOMPLETELISTENER_IMPL_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "third_party/WebKit/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/WebKit/chromium/public/WebPasswordAutocompleteListener.h" +#include "webkit/glue/password_form_dom_manager.h" + +using WebKit::WebInputElement; +using WebKit::WebString; + +namespace webkit_glue { + +// A proxy interface to a WebInputElement for inline autocomplete. The proxy +// is overridden by webpasswordautocompletelistener_unittest. +class WebInputElementDelegate { + public: + WebInputElementDelegate(); + WebInputElementDelegate(const WebInputElement& element); + virtual ~WebInputElementDelegate(); + + // These are virtual to support unit testing. + virtual bool IsEditable() const; + virtual void SetValue(const string16& value); + virtual bool IsAutofilled() const; + virtual void SetAutofilled(bool autofilled); + virtual void SetSelectionRange(size_t start, size_t end); + virtual void RefreshAutofillPopup(const std::vector<string16>& suggestions); + + private: + // The underlying DOM element we're wrapping. + WebInputElement element_; + + DISALLOW_COPY_AND_ASSIGN(WebInputElementDelegate); +}; + +class WebPasswordAutocompleteListenerImpl : + public WebKit::WebPasswordAutocompleteListener { + public: + WebPasswordAutocompleteListenerImpl( + WebInputElementDelegate* username_element, + WebInputElementDelegate* password_element, + const PasswordFormFillData& data); + ~WebPasswordAutocompleteListenerImpl() { + } + + // WebKit::PasswordAutocompleteListener methods: + virtual void didBlurInputElement(const WebString& user_input); + virtual void performInlineAutocomplete(const WebString& user_input, + bool backspace_or_delete_pressed, + bool show_suggestions); + virtual bool showSuggestionPopup(const WebString& value); + + private: + // Check if the input string resembles a potential matching login + // (username/password) and if so, match them up by autocompleting the edit + // delegates. + bool TryToMatch(const string16& input, + const string16& username, + const string16& password); + + // Scan |data_| for prefix matches of |input| and add each to |suggestions|. + void GetSuggestions(const string16& input, + std::vector<string16>* suggestions); + + // Access to password field to autocomplete on blur/username updates. + scoped_ptr<WebInputElementDelegate> password_delegate_; + scoped_ptr<WebInputElementDelegate> username_delegate_; + + // Contains the extra logins for matching on delta/blur. + PasswordFormFillData data_; + + DISALLOW_COPY_AND_ASSIGN(WebPasswordAutocompleteListenerImpl); +}; + +} // webkit_glue + +#endif // WEBKIT_GLUE_PASSWORD_AUTOCOMPLETE_LISTENER_H_ diff --git a/webkit/glue/webpasswordautocompletelistener_unittest.cc b/webkit/glue/webpasswordautocompletelistener_unittest.cc new file mode 100644 index 0000000..5a05d46 --- /dev/null +++ b/webkit/glue/webpasswordautocompletelistener_unittest.cc @@ -0,0 +1,291 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The PasswordManagerAutocompleteTests in this file test only the +// PasswordAutocompleteListener class implementation (and not any of the +// higher level dom autocomplete framework). + +#include <string> + +#include "base/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/form_field.h" +#include "webkit/glue/webpasswordautocompletelistener_impl.h" + +using WebKit::WebString; +using webkit_glue::FormField; +using webkit_glue::PasswordFormDomManager; +using webkit_glue::PasswordFormFillData; +using webkit_glue::WebInputElementDelegate; +using webkit_glue::WebPasswordAutocompleteListenerImpl; + +class TestWebInputElementDelegate : public WebInputElementDelegate { + public: + TestWebInputElementDelegate() + : WebInputElementDelegate(), + selection_start_(0), + selection_end_(0), + is_editable_(true), + is_autofilled_(false) { + } + + // Override those methods we implicitly invoke in the tests. + virtual bool IsEditable() const { + return is_editable_; + } + + virtual void SetValue(const string16& value) { + value_ = value; + } + + virtual bool IsAutofilled() const { return is_autofilled_; } + + virtual void SetAutofilled(bool autofilled) { + is_autofilled_ = autofilled; + } + + virtual void SetSelectionRange(size_t start, size_t end) { + selection_start_ = start; + selection_end_ = end; + } + + // Testing-only methods. + void set_is_editable(bool editable) { + is_editable_ = editable; + } + + string16 value() const { + return value_; + } + + size_t selection_start() const { + return selection_start_; + } + + size_t selection_end() const { + return selection_end_; + } + + private: + string16 value_; + size_t selection_start_; + size_t selection_end_; + bool is_editable_; + bool is_autofilled_; +}; + +namespace { +class PasswordManagerAutocompleteTests : public testing::Test { + public: + PasswordManagerAutocompleteTests() + : username_delegate_(NULL), + password_delegate_(NULL) { + } + + virtual void SetUp() { + // Add a preferred login and an additional login to the FillData. + username1_ = ASCIIToUTF16("alice"); + password1_ = ASCIIToUTF16("password"); + username2_ = ASCIIToUTF16("bob"); + password2_ = ASCIIToUTF16("bobsyouruncle"); + data_.basic_data.fields.push_back(FormField(string16(), + string16(), + username1_, + string16(), + 0)); + data_.basic_data.fields.push_back(FormField(string16(), + string16(), + password1_, + string16(), + 0)); + data_.additional_logins[username2_] = password2_; + + username_delegate_ = new TestWebInputElementDelegate(); + password_delegate_ = new TestWebInputElementDelegate(); + + testing::Test::SetUp(); + } + + protected: + // Create the WebPasswordAutocompleteListener associated with + // username_delegate_ and password_delegate_, should be called only once at + // the begining of the test. + WebKit::WebPasswordAutocompleteListener* CreateListener( + bool wait_for_username) { + DCHECK(!listener_.get()); + data_.wait_for_username = wait_for_username; + listener_.reset(new WebPasswordAutocompleteListenerImpl(username_delegate_, + password_delegate_, + data_)); + return listener_.get(); + } + + string16 username1_; + string16 password1_; + string16 username2_; + string16 password2_; + PasswordFormFillData data_; + TestWebInputElementDelegate* username_delegate_; + TestWebInputElementDelegate* password_delegate_; + + private: + scoped_ptr<WebPasswordAutocompleteListenerImpl> listener_; +}; + +TEST_F(PasswordManagerAutocompleteTests, OnBlur) { + WebKit::WebPasswordAutocompleteListener* listener = CreateListener(false); + + // Make the password field read-only. + password_delegate_->set_is_editable(false); + // Simulate a blur event on the username field, but r/o password won't fill. + listener->didBlurInputElement(username1_); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + password_delegate_->set_is_editable(true); + + // Simulate a blur event on the username field and expect a password autofill. + listener->didBlurInputElement(username1_); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + + // Now the user goes back and changes the username to something we don't + // have saved. The password should remain unchanged. + listener->didBlurInputElement(ASCIIToUTF16("blahblahblah")); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + + // Now they type in the additional login username. + listener->didBlurInputElement(username2_); + EXPECT_EQ(password2_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); +} + +TEST_F(PasswordManagerAutocompleteTests, OnInlineAutocompleteNeeded) { + WebKit::WebPasswordAutocompleteListener* listener = CreateListener(false); + + // Simulate the user typing in the first letter of 'alice', a stored username. + listener->performInlineAutocomplete(ASCIIToUTF16("a"), false, false); + // Both the username and password delegates should reflect selection + // of the stored login. + EXPECT_EQ(username1_, username_delegate_->value()); + EXPECT_TRUE(username_delegate_->IsAutofilled()); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + // And the selection should have been set to 'lice', the last 4 letters. + EXPECT_EQ(1U, username_delegate_->selection_start()); + EXPECT_EQ(username1_.length(), username_delegate_->selection_end()); + // And both fields should have the autofill style. + EXPECT_TRUE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + + // Now the user types the next letter of the same username, 'l'. + listener->performInlineAutocomplete(ASCIIToUTF16("al"), false, false); + // Now the fields should have the same value, but the selection should have a + // different start value. + EXPECT_EQ(username1_, username_delegate_->value()); + EXPECT_TRUE(username_delegate_->IsAutofilled()); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + EXPECT_EQ(2U, username_delegate_->selection_start()); + EXPECT_EQ(username1_.length(), username_delegate_->selection_end()); + + // Now lets say the user goes astray from the stored username and types + // the letter 'f', spelling 'alf'. We don't know alf (that's just sad), + // so in practice the username should no longer be 'alice' and the selected + // range should be empty. In our case, when the autocomplete code doesn't + // know the text, it won't set the value or the selection and hence our + // delegate methods won't get called. The WebCore::HTMLInputElement's value + // and selection would be set directly by WebCore in practice. + + // Reset the delegate's test state so we can determine what, if anything, + // was set during performInlineAutocomplete. + username_delegate_->SetValue(string16()); + username_delegate_->SetSelectionRange(0, 0); + listener->performInlineAutocomplete(ASCIIToUTF16("alf"), false, false); + EXPECT_EQ(0U, username_delegate_->selection_start()); + EXPECT_EQ(0U, username_delegate_->selection_end()); + // Username should not have been filled by us. + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + + // Ok, so now the user removes all the text and enters the letter 'b'. + listener->performInlineAutocomplete(ASCIIToUTF16("b"), false, false); + // The username and password fields should match the 'bob' entry. + EXPECT_EQ(username2_, username_delegate_->value()); + EXPECT_TRUE(username_delegate_->IsAutofilled()); + EXPECT_EQ(password2_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + EXPECT_EQ(1U, username_delegate_->selection_start()); + EXPECT_EQ(username2_.length(), username_delegate_->selection_end()); +} + +TEST_F(PasswordManagerAutocompleteTests, TestWaitUsername) { + // If we had an action authority mismatch (for example), we don't want to + // automatically autofill anything without some user interaction first. + // We require an explicit blur on the username field, and that a valid + // matching username is in the field, before we autofill passwords. + WebKit::WebPasswordAutocompleteListener* listener = CreateListener(true); + + // In all cases, username_delegate should remain empty because we should + // never modify it when wait_for_username is true; only the user can by + // typing into (in real life) the HTMLInputElement. + password_delegate_->SetValue(string16()); + listener->performInlineAutocomplete(ASCIIToUTF16("a"), false, false); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + listener->performInlineAutocomplete(ASCIIToUTF16("al"), false, false); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + listener->performInlineAutocomplete(ASCIIToUTF16("alice"), false, false); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + + listener->didBlurInputElement(ASCIIToUTF16("a")); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + listener->didBlurInputElement(ASCIIToUTF16("ali")); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + + // Blur with 'alice' should allow password autofill. + listener->didBlurInputElement(ASCIIToUTF16("alice")); + EXPECT_TRUE(username_delegate_->value().empty()); + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); +} + +// Tests that editing the password clears the autofilled password field. +TEST_F(PasswordManagerAutocompleteTests, TestPasswordClearOnEdit) { + WebKit::WebPasswordAutocompleteListener* listener = CreateListener(false); + + // User enters a known login. + listener->performInlineAutocomplete(ASCIIToUTF16("alice"), false, false); + // We are autofilled. + EXPECT_TRUE(username_delegate_->IsAutofilled()); + EXPECT_EQ(password1_, password_delegate_->value()); + EXPECT_TRUE(password_delegate_->IsAutofilled()); + + // User modifies the login name to an unknown one. + listener->performInlineAutocomplete(ASCIIToUTF16("alicia"), false, false); + // We should not be autofilled anymore and the password should have been + // cleared. + EXPECT_FALSE(username_delegate_->IsAutofilled()); + EXPECT_FALSE(password_delegate_->IsAutofilled()); + EXPECT_TRUE(password_delegate_->value().empty()); +} + +} // namespace diff --git a/webkit/glue/webpreferences.cc b/webkit/glue/webpreferences.cc new file mode 100644 index 0000000..987cef4 --- /dev/null +++ b/webkit/glue/webpreferences.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/webpreferences.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRuntimeFeatures.h" +#include "third_party/WebKit/WebKit/chromium/public/WebKit.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSettings.h" +#include "third_party/WebKit/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/glue/webkit_glue.h" + +using WebKit::WebRuntimeFeatures; +using WebKit::WebSettings; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebView; + +void WebPreferences::Apply(WebView* web_view) const { + WebSettings* settings = web_view->settings(); + settings->setStandardFontFamily(WideToUTF16Hack(standard_font_family)); + settings->setFixedFontFamily(WideToUTF16Hack(fixed_font_family)); + settings->setSerifFontFamily(WideToUTF16Hack(serif_font_family)); + settings->setSansSerifFontFamily(WideToUTF16Hack(sans_serif_font_family)); + settings->setCursiveFontFamily(WideToUTF16Hack(cursive_font_family)); + settings->setFantasyFontFamily(WideToUTF16Hack(fantasy_font_family)); + settings->setDefaultFontSize(default_font_size); + settings->setDefaultFixedFontSize(default_fixed_font_size); + settings->setMinimumFontSize(minimum_font_size); + settings->setMinimumLogicalFontSize(minimum_logical_font_size); + settings->setDefaultTextEncodingName(ASCIIToUTF16(default_encoding)); + settings->setJavaScriptEnabled(javascript_enabled); + settings->setWebSecurityEnabled(web_security_enabled); + settings->setJavaScriptCanOpenWindowsAutomatically( + javascript_can_open_windows_automatically); + settings->setLoadsImagesAutomatically(loads_images_automatically); + settings->setPluginsEnabled(plugins_enabled); + settings->setDOMPasteAllowed(dom_paste_enabled); + settings->setDeveloperExtrasEnabled(developer_extras_enabled); + settings->setNeedsSiteSpecificQuirks(site_specific_quirks_enabled); + settings->setShrinksStandaloneImagesToFit(shrinks_standalone_images_to_fit); + settings->setUsesEncodingDetector(uses_universal_detector); + settings->setTextAreasAreResizable(text_areas_are_resizable); + settings->setAllowScriptsToCloseWindows(allow_scripts_to_close_windows); + if (user_style_sheet_enabled) + settings->setUserStyleSheetLocation(user_style_sheet_location); + else + settings->setUserStyleSheetLocation(WebURL()); + settings->setAuthorAndUserStylesEnabled(author_and_user_styles_enabled); + settings->setUsesPageCache(uses_page_cache); + settings->setDownloadableBinaryFontsEnabled(remote_fonts_enabled); + settings->setJavaScriptCanAccessClipboard(javascript_can_access_clipboard); + settings->setXSSAuditorEnabled(xss_auditor_enabled); + settings->setLocalStorageEnabled(local_storage_enabled); + WebRuntimeFeatures::enableDatabase( + WebRuntimeFeatures::isDatabaseEnabled() || databases_enabled); + settings->setOfflineWebApplicationCacheEnabled(application_cache_enabled); + settings->setHTML5ParserEnabled(enable_html5_parser); + + // This setting affects the behavior of links in an editable region: + // clicking the link should select it rather than navigate to it. + // Safari uses the same default. It is unlikley an embedder would want to + // change this, since it would break existing rich text editors. + settings->setEditableLinkBehaviorNeverLive(); + + settings->setFontRenderingModeNormal(); + settings->setJavaEnabled(java_enabled); + + // Turn this on to cause WebCore to paint the resize corner for us. + settings->setShouldPaintCustomScrollbars(true); + + // By default, allow_universal_access_from_file_urls is set to false and thus + // we mitigate attacks from local HTML files by not granting file:// URLs + // universal access. Only test shell will enable this. + settings->setAllowUniversalAccessFromFileURLs( + allow_universal_access_from_file_urls); + settings->setAllowFileAccessFromFileURLs(allow_file_access_from_file_urls); + + // We prevent WebKit from checking if it needs to add a "text direction" + // submenu to a context menu. it is not only because we don't need the result + // but also because it cause a possible crash in Editor::hasBidiSelection(). + settings->setTextDirectionSubmenuInclusionBehaviorNeverIncluded(); + + // Enable experimental WebGL support if requested on command line + // and support is compiled in. + settings->setExperimentalWebGLEnabled(experimental_webgl_enabled); + + // Display colored borders around composited render layers if requested + // on command line. + settings->setShowDebugBorders(show_composited_layer_borders); + + // Enable gpu-accelerated compositing if requested on the command line. + settings->setAcceleratedCompositingEnabled(accelerated_compositing_enabled); + + // Enable memory info reporting to page if requested on the command line. + settings->setMemoryInfoEnabled(memory_info_enabled); + + for (WebInspectorPreferences::const_iterator it = inspector_settings.begin(); + it != inspector_settings.end(); ++it) + web_view->setInspectorSetting(WebString::fromUTF8(it->first), + WebString::fromUTF8(it->second)); + + // Tabs to link is not part of the settings. WebCore calls + // ChromeClient::tabsToLinks which is part of the glue code. + web_view->setTabsToLinks(tabs_to_links); +} diff --git a/webkit/glue/webpreferences.h b/webkit/glue/webpreferences.h new file mode 100644 index 0000000..3e4f926 --- /dev/null +++ b/webkit/glue/webpreferences.h @@ -0,0 +1,120 @@ +// Copyright (c) 2006-2008 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. +// +// A struct for managing webkit's settings. +// +// Adding new values to this class probably involves updating +// WebKit::WebSettings, common/render_messages.h, and +// browser/profile.cc. + +#ifndef WEBKIT_GLUE_WEBPREFERENCES_H__ +#define WEBKIT_GLUE_WEBPREFERENCES_H__ + +#include <string> +#include <vector> +#include "googleurl/src/gurl.h" + +namespace WebKit { +class WebView; +} + +struct WebPreferences { + std::wstring standard_font_family; + std::wstring fixed_font_family; + std::wstring serif_font_family; + std::wstring sans_serif_font_family; + std::wstring cursive_font_family; + std::wstring fantasy_font_family; + int default_font_size; + int default_fixed_font_size; + int minimum_font_size; + int minimum_logical_font_size; + std::string default_encoding; + bool javascript_enabled; + bool web_security_enabled; + bool javascript_can_open_windows_automatically; + bool loads_images_automatically; + bool plugins_enabled; + bool dom_paste_enabled; + bool developer_extras_enabled; + typedef std::vector<std::pair<std::string, std::string> > + WebInspectorPreferences; + WebInspectorPreferences inspector_settings; + bool site_specific_quirks_enabled; + bool shrinks_standalone_images_to_fit; + bool uses_universal_detector; + bool text_areas_are_resizable; + bool java_enabled; + bool allow_scripts_to_close_windows; + bool uses_page_cache; + bool remote_fonts_enabled; + bool javascript_can_access_clipboard; + bool xss_auditor_enabled; + bool local_storage_enabled; + bool databases_enabled; + bool application_cache_enabled; + bool tabs_to_links; + + bool user_style_sheet_enabled; + GURL user_style_sheet_location; + bool author_and_user_styles_enabled; + bool allow_universal_access_from_file_urls; + bool allow_file_access_from_file_urls; + bool experimental_webgl_enabled; + bool show_composited_layer_borders; + bool accelerated_compositing_enabled; + bool enable_html5_parser; + bool memory_info_enabled; + + // We try to keep the default values the same as the default values in + // chrome, except for the cases where it would require lots of extra work for + // the embedder to use the same default value. + WebPreferences() + : standard_font_family(L"Times New Roman"), + fixed_font_family(L"Courier New"), + serif_font_family(L"Times New Roman"), + sans_serif_font_family(L"Arial"), + cursive_font_family(L"Script"), + fantasy_font_family(), // Not sure what to use on Windows. + default_font_size(16), + default_fixed_font_size(13), + minimum_font_size(1), + minimum_logical_font_size(6), + default_encoding("ISO-8859-1"), + javascript_enabled(true), + web_security_enabled(true), + javascript_can_open_windows_automatically(true), + loads_images_automatically(true), + plugins_enabled(true), + dom_paste_enabled(false), // enables execCommand("paste") + developer_extras_enabled(false), // Requires extra work by embedder + site_specific_quirks_enabled(false), + shrinks_standalone_images_to_fit(true), + uses_universal_detector(false), // Disabled: page cycler regression + text_areas_are_resizable(true), + java_enabled(true), + allow_scripts_to_close_windows(false), + uses_page_cache(false), + remote_fonts_enabled(true), + javascript_can_access_clipboard(false), + xss_auditor_enabled(false), + local_storage_enabled(false), + databases_enabled(false), + application_cache_enabled(false), + tabs_to_links(true), + user_style_sheet_enabled(false), + author_and_user_styles_enabled(true), + allow_universal_access_from_file_urls(false), + allow_file_access_from_file_urls(false), + experimental_webgl_enabled(false), + show_composited_layer_borders(false), + accelerated_compositing_enabled(false), + enable_html5_parser(true), + memory_info_enabled(false) { + } + + void Apply(WebKit::WebView* web_view) const; +}; + +#endif // WEBKIT_GLUE_WEBPREFERENCES_H__ diff --git a/webkit/glue/websocketstreamhandle_bridge.h b/webkit/glue/websocketstreamhandle_bridge.h new file mode 100644 index 0000000..ee897fd --- /dev/null +++ b/webkit/glue/websocketstreamhandle_bridge.h @@ -0,0 +1,47 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_BRIDGE_H_ +#define WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_BRIDGE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +class GURL; + +namespace WebKit { +class WebSocketStreamHandle; +} + +namespace webkit_glue { + +class WebSocketStreamHandleDelegate; + +class WebSocketStreamHandleBridge + : public base::RefCountedThreadSafe<WebSocketStreamHandleBridge> { + public: + static WebSocketStreamHandleBridge* Create( + WebKit::WebSocketStreamHandle* handle, + WebSocketStreamHandleDelegate* delegate); + + virtual void Connect(const GURL& url) = 0; + + virtual bool Send(const std::vector<char>& data) = 0; + + virtual void Close() = 0; + + protected: + friend class base::RefCountedThreadSafe<WebSocketStreamHandleBridge>; + WebSocketStreamHandleBridge() {} + virtual ~WebSocketStreamHandleBridge() {} + + private: + DISALLOW_COPY_AND_ASSIGN(WebSocketStreamHandleBridge); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_BRIDGE_H_ diff --git a/webkit/glue/websocketstreamhandle_delegate.h b/webkit/glue/websocketstreamhandle_delegate.h new file mode 100644 index 0000000..2148699 --- /dev/null +++ b/webkit/glue/websocketstreamhandle_delegate.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_DELEGATE_H_ +#define WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_DELEGATE_H_ + +class GURL; + +namespace WebKit { +class WebSocketStreamHandle; +} + +namespace webkit_glue { + +class WebSocketStreamHandleDelegate { + public: + WebSocketStreamHandleDelegate() {} + virtual ~WebSocketStreamHandleDelegate() {} + + virtual void WillOpenStream(WebKit::WebSocketStreamHandle* handle, + const GURL& url) {} + virtual void WillSendData(WebKit::WebSocketStreamHandle* handle, + const char* data, int len) {} + + virtual void DidOpenStream(WebKit::WebSocketStreamHandle* handle, + int max_amount_send_allowed) {} + virtual void DidSendData(WebKit::WebSocketStreamHandle* handle, + int amount_sent) {} + virtual void DidReceiveData(WebKit::WebSocketStreamHandle* handle, + const char* data, int len) {} + virtual void DidClose(WebKit::WebSocketStreamHandle*) {} +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_DELEGATE_H_ diff --git a/webkit/glue/websocketstreamhandle_impl.cc b/webkit/glue/websocketstreamhandle_impl.cc new file mode 100644 index 0000000..60faea2 --- /dev/null +++ b/webkit/glue/websocketstreamhandle_impl.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// An implementation of WebSocketStreamHandle. + +#include "webkit/glue/websocketstreamhandle_impl.h" + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "third_party/WebKit/WebKit/chromium/public/WebData.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSocketStreamHandleClient.h" +#include "webkit/glue/websocketstreamhandle_bridge.h" +#include "webkit/glue/websocketstreamhandle_delegate.h" + +namespace webkit_glue { + +// WebSocketStreamHandleImpl::Context ----------------------------------------- + +class WebSocketStreamHandleImpl::Context + : public base::RefCounted<Context>, + public WebSocketStreamHandleDelegate { + public: + explicit Context(WebSocketStreamHandleImpl* handle); + + WebKit::WebSocketStreamHandleClient* client() const { return client_; } + void set_client(WebKit::WebSocketStreamHandleClient* client) { + client_ = client; + } + + void Connect(const WebKit::WebURL& url); + bool Send(const WebKit::WebData& data); + void Close(); + + // Must be called before |handle_| or |client_| is deleted. + // Once detached, it never calls |client_| back. + void Detach(); + + // WebSocketStreamHandleDelegate methods: + virtual void DidOpenStream(WebKit::WebSocketStreamHandle*, int); + virtual void DidSendData(WebKit::WebSocketStreamHandle*, int); + virtual void DidReceiveData( + WebKit::WebSocketStreamHandle*, const char*, int); + virtual void DidClose(WebKit::WebSocketStreamHandle*); + + private: + friend class base::RefCounted<Context>; + ~Context() { + DCHECK(!handle_); + DCHECK(!client_); + DCHECK(!bridge_); + } + + WebSocketStreamHandleImpl* handle_; + WebKit::WebSocketStreamHandleClient* client_; + // |bridge_| is alive from Connect to DidClose, so Context must be alive + // in the time period. + scoped_refptr<WebSocketStreamHandleBridge> bridge_; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +WebSocketStreamHandleImpl::Context::Context(WebSocketStreamHandleImpl* handle) + : handle_(handle), + client_(NULL), + bridge_(NULL) { +} + +void WebSocketStreamHandleImpl::Context::Connect(const WebKit::WebURL& url) { + LOG(INFO) << "Connect url=" << url; + DCHECK(!bridge_); + bridge_ = WebSocketStreamHandleBridge::Create(handle_, this); + AddRef(); // Will be released by DidClose(). + bridge_->Connect(url); +} + +bool WebSocketStreamHandleImpl::Context::Send(const WebKit::WebData& data) { + LOG(INFO) << "Send data.size=" << data.size(); + DCHECK(bridge_); + return bridge_->Send( + std::vector<char>(data.data(), data.data() + data.size())); +} + +void WebSocketStreamHandleImpl::Context::Close() { + LOG(INFO) << "Close"; + if (bridge_) + bridge_->Close(); +} + +void WebSocketStreamHandleImpl::Context::Detach() { + handle_ = NULL; + client_ = NULL; + // If Connect was called, |bridge_| is not NULL, so that this Context closes + // the |bridge_| here. Then |bridge_| will call back DidClose, and will + // be released by itself. + // Otherwise, |bridge_| is NULL. + if (bridge_) + bridge_->Close(); +} + +void WebSocketStreamHandleImpl::Context::DidOpenStream( + WebKit::WebSocketStreamHandle* web_handle, int max_amount_send_allowed) { + LOG(INFO) << "DidOpen"; + if (client_) + client_->didOpenStream(handle_, max_amount_send_allowed); +} + +void WebSocketStreamHandleImpl::Context::DidSendData( + WebKit::WebSocketStreamHandle* web_handle, int amount_sent) { + if (client_) + client_->didSendData(handle_, amount_sent); +} + +void WebSocketStreamHandleImpl::Context::DidReceiveData( + WebKit::WebSocketStreamHandle* web_handle, const char* data, int size) { + if (client_) + client_->didReceiveData(handle_, WebKit::WebData(data, size)); +} + +void WebSocketStreamHandleImpl::Context::DidClose( + WebKit::WebSocketStreamHandle* web_handle) { + LOG(INFO) << "DidClose"; + bridge_ = NULL; + WebSocketStreamHandleImpl* handle = handle_; + handle_ = NULL; + if (client_) { + WebKit::WebSocketStreamHandleClient* client = client_; + client_ = NULL; + client->didClose(handle); + } + Release(); +} + +// WebSocketStreamHandleImpl ------------------------------------------------ + +WebSocketStreamHandleImpl::WebSocketStreamHandleImpl() + : ALLOW_THIS_IN_INITIALIZER_LIST(context_(new Context(this))) { +} + +WebSocketStreamHandleImpl::~WebSocketStreamHandleImpl() { + // We won't receive any events from |context_|. + // |context_| is ref counted, and will be released when it received + // DidClose. + context_->Detach(); +} + +void WebSocketStreamHandleImpl::connect( + const WebKit::WebURL& url, WebKit::WebSocketStreamHandleClient* client) { + LOG(INFO) << "connect url=" << url; + DCHECK(!context_->client()); + context_->set_client(client); + + context_->Connect(url); +} + +bool WebSocketStreamHandleImpl::send(const WebKit::WebData& data) { + return context_->Send(data); +} + +void WebSocketStreamHandleImpl::close() { + context_->Close(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/websocketstreamhandle_impl.h b/webkit/glue/websocketstreamhandle_impl.h new file mode 100644 index 0000000..f8a08f4 --- /dev/null +++ b/webkit/glue/websocketstreamhandle_impl.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_IMPL_H_ +#define WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_IMPL_H_ + +#include "base/ref_counted.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSocketStreamHandle.h" + +namespace webkit_glue { + +class WebSocketStreamHandleImpl : public WebKit::WebSocketStreamHandle { + public: + WebSocketStreamHandleImpl(); + virtual ~WebSocketStreamHandleImpl(); + + // WebSocketStreamHandle methods: + virtual void connect( + const WebKit::WebURL& url, + WebKit::WebSocketStreamHandleClient* client); + virtual bool send(const WebKit::WebData& data); + virtual void close(); + + private: + class Context; + scoped_refptr<Context> context_; + + DISALLOW_COPY_AND_ASSIGN(WebSocketStreamHandleImpl); +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBSOCKETSTREAMHANDLE_IMPL_H_ diff --git a/webkit/glue/webthemeengine_impl_win.cc b/webkit/glue/webthemeengine_impl_win.cc new file mode 100644 index 0000000..85dc113 --- /dev/null +++ b/webkit/glue/webthemeengine_impl_win.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2010 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 "webkit/glue/webthemeengine_impl_win.h" + +#include "gfx/native_theme_win.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/skia_utils_win.h" +#include "third_party/WebKit/WebKit/chromium/public/WebRect.h" + +using WebKit::WebCanvas; +using WebKit::WebColor; +using WebKit::WebRect; + +namespace webkit_glue { + +static RECT WebRectToRECT(const WebRect& rect) { + RECT result; + result.left = rect.x; + result.top = rect.y; + result.right = rect.x + rect.width; + result.bottom = rect.y + rect.height; + return result; +} + +void WebThemeEngineImpl::paintButton( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintButton( + hdc, part, state, classic_state, &native_rect); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintMenuList( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintMenuList( + hdc, part, state, classic_state, &native_rect); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintScrollbarArrow( + WebCanvas* canvas, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintScrollbarArrow( + hdc, state, classic_state, &native_rect); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintScrollbarThumb( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintScrollbarThumb( + hdc, part, state, classic_state, &native_rect); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintScrollbarTrack( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect, const WebRect& align_rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + RECT native_align_rect = WebRectToRECT(align_rect); + gfx::NativeTheme::instance()->PaintScrollbarTrack( + hdc, part, state, classic_state, &native_rect, &native_align_rect, + canvas); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintSpinButton( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintSpinButton( + hdc, part, state, classic_state, &native_rect); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintTextField( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect, WebColor color, bool fill_content_area, + bool draw_edges) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + COLORREF c = skia::SkColorToCOLORREF(color); + + gfx::NativeTheme::instance()->PaintTextField( + hdc, part, state, classic_state, &native_rect, c, fill_content_area, + draw_edges); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintTrackbar( + WebCanvas* canvas, int part, int state, int classic_state, + const WebRect& rect) { + HDC hdc = canvas->beginPlatformPaint(); + + RECT native_rect = WebRectToRECT(rect); + gfx::NativeTheme::instance()->PaintTrackbar( + hdc, part, state, classic_state, &native_rect, canvas); + + canvas->endPlatformPaint(); +} + +void WebThemeEngineImpl::paintProgressBar( + WebCanvas* canvas, const WebRect& barRect, const WebRect& valueRect, + bool determinate, double animatedSeconds) +{ + HDC hdc = canvas->beginPlatformPaint(); + RECT native_bar_rect = WebRectToRECT(barRect); + RECT native_value_rect = WebRectToRECT(valueRect); + gfx::NativeTheme::instance()->PaintProgressBar( + hdc, &native_bar_rect, + &native_value_rect, determinate, animatedSeconds, canvas); + canvas->endPlatformPaint(); +} + +} // namespace webkit_glue diff --git a/webkit/glue/webthemeengine_impl_win.h b/webkit/glue/webthemeengine_impl_win.h new file mode 100644 index 0000000..533ea19 --- /dev/null +++ b/webkit/glue/webthemeengine_impl_win.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 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 WEBTHEMEENGINE_IMPL_WIN_H_ +#define WEBTHEMEENGINE_IMPL_WIN_H_ + +#include "third_party/WebKit/WebKit/chromium/public/WebThemeEngine.h" + +namespace webkit_glue { + +class WebThemeEngineImpl : public WebKit::WebThemeEngine { + public: + // WebThemeEngine methods: + virtual void paintButton( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintMenuList( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintScrollbarArrow( + WebKit::WebCanvas*, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintScrollbarThumb( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintScrollbarTrack( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&, const WebKit::WebRect& align_rect); + virtual void paintSpinButton( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintTextField( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&, WebKit::WebColor, bool fill_content_area, + bool draw_edges); + virtual void paintTrackbar( + WebKit::WebCanvas*, int part, int state, int classic_state, + const WebKit::WebRect&); + virtual void paintProgressBar( + WebKit::WebCanvas*, const WebKit::WebRect& barRect, + const WebKit::WebRect& valueRect, bool determinate, + double animatedSeconds); +}; + +} // namespace webkit_glue + +#endif // WEBTHEMEENGINE_IMPL_WIN_H_ diff --git a/webkit/glue/weburlloader_impl.cc b/webkit/glue/weburlloader_impl.cc new file mode 100644 index 0000000..f235734 --- /dev/null +++ b/webkit/glue/weburlloader_impl.cc @@ -0,0 +1,688 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// An implementation of WebURLLoader in terms of ResourceLoaderBridge. + +#include "webkit/glue/weburlloader_impl.h" + +#include "base/file_path.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/time.h" +#include "net/base/data_url.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/http/http_response_headers.h" +#include "third_party/WebKit/WebKit/chromium/public/WebHTTPHeaderVisitor.h" +#include "third_party/WebKit/WebKit/chromium/public/WebSecurityPolicy.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURL.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLError.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoadTiming.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoaderClient.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLResponse.h" +#include "webkit/glue/ftp_directory_listing_response_delegate.h" +#include "webkit/glue/multipart_response_delegate.h" +#include "webkit/glue/resource_loader_bridge.h" +#include "webkit/glue/site_isolation_metrics.h" +#include "webkit/glue/webkit_glue.h" + +using base::Time; +using base::TimeDelta; +using WebKit::WebData; +using WebKit::WebHTTPBody; +using WebKit::WebHTTPHeaderVisitor; +using WebKit::WebSecurityPolicy; +using WebKit::WebString; +using WebKit::WebURL; +using WebKit::WebURLError; +using WebKit::WebURLLoadTiming; +using WebKit::WebURLLoader; +using WebKit::WebURLLoaderClient; +using WebKit::WebURLRequest; +using WebKit::WebURLResponse; + +namespace webkit_glue { + +// Utilities ------------------------------------------------------------------ + +namespace { + +class HeaderFlattener : public WebHTTPHeaderVisitor { + public: + explicit HeaderFlattener(int load_flags) + : load_flags_(load_flags), + has_accept_header_(false) { + } + + virtual void visitHeader(const WebString& name, const WebString& value) { + // TODO(darin): is UTF-8 really correct here? It is if the strings are + // already ASCII (i.e., if they are already escaped properly). + const std::string& name_utf8 = name.utf8(); + const std::string& value_utf8 = value.utf8(); + + // Skip over referrer headers found in the header map because we already + // pulled it out as a separate parameter. We likewise prune the UA since + // that will be added back by the network layer. + if (LowerCaseEqualsASCII(name_utf8, "referer") || + LowerCaseEqualsASCII(name_utf8, "user-agent")) + return; + + // Skip over "Cache-Control: max-age=0" header if the corresponding + // load flag is already specified. FrameLoader sets both the flag and + // the extra header -- the extra header is redundant since our network + // implementation will add the necessary headers based on load flags. + // See http://code.google.com/p/chromium/issues/detail?id=3434. + if ((load_flags_ & net::LOAD_VALIDATE_CACHE) && + LowerCaseEqualsASCII(name_utf8, "cache-control") && + LowerCaseEqualsASCII(value_utf8, "max-age=0")) + return; + + if (LowerCaseEqualsASCII(name_utf8, "accept")) + has_accept_header_ = true; + + if (!buffer_.empty()) + buffer_.append("\r\n"); + buffer_.append(name_utf8 + ": " + value_utf8); + } + + const std::string& GetBuffer() { + // In some cases, WebKit doesn't add an Accept header, but not having the + // header confuses some web servers. See bug 808613. + if (!has_accept_header_) { + if (!buffer_.empty()) + buffer_.append("\r\n"); + buffer_.append("Accept: */*"); + has_accept_header_ = true; + } + return buffer_; + } + + private: + int load_flags_; + std::string buffer_; + bool has_accept_header_; +}; + +ResourceType::Type FromTargetType(WebURLRequest::TargetType type) { + switch (type) { + case WebURLRequest::TargetIsMainFrame: + return ResourceType::MAIN_FRAME; + case WebURLRequest::TargetIsSubframe: + return ResourceType::SUB_FRAME; + case WebURLRequest::TargetIsSubresource: + return ResourceType::SUB_RESOURCE; + case WebURLRequest::TargetIsStyleSheet: + return ResourceType::STYLESHEET; + case WebURLRequest::TargetIsScript: + return ResourceType::SCRIPT; + case WebURLRequest::TargetIsFontResource: + return ResourceType::FONT_RESOURCE; + case WebURLRequest::TargetIsImage: + return ResourceType::IMAGE; + case WebURLRequest::TargetIsObject: + return ResourceType::OBJECT; + case WebURLRequest::TargetIsMedia: + return ResourceType::MEDIA; + case WebURLRequest::TargetIsWorker: + return ResourceType::WORKER; + case WebURLRequest::TargetIsSharedWorker: + return ResourceType::SHARED_WORKER; + default: + NOTREACHED(); + return ResourceType::SUB_RESOURCE; + } +} + +// Extracts the information from a data: url. +bool GetInfoFromDataURL(const GURL& url, + ResourceLoaderBridge::ResponseInfo* info, + std::string* data, URLRequestStatus* status) { + std::string mime_type; + std::string charset; + if (net::DataURL::Parse(url, &mime_type, &charset, data)) { + *status = URLRequestStatus(URLRequestStatus::SUCCESS, 0); + info->request_time = Time::Now(); + info->response_time = Time::Now(); + info->headers = NULL; + info->mime_type.swap(mime_type); + info->charset.swap(charset); + info->security_info.clear(); + info->content_length = -1; + + return true; + } + + *status = URLRequestStatus(URLRequestStatus::FAILED, net::ERR_INVALID_URL); + return false; +} + +void PopulateURLResponse( + const GURL& url, + const ResourceLoaderBridge::ResponseInfo& info, + WebURLResponse* response) { + response->setURL(url); + response->setResponseTime(info.response_time.ToDoubleT()); + response->setMIMEType(WebString::fromUTF8(info.mime_type)); + response->setTextEncodingName(WebString::fromUTF8(info.charset)); + response->setExpectedContentLength(info.content_length); + response->setSecurityInfo(info.security_info); + response->setAppCacheID(info.appcache_id); + response->setAppCacheManifestURL(info.appcache_manifest_url); + response->setWasCached(!info.load_timing.base_time.is_null() && + info.response_time < info.load_timing.base_time); + response->setWasFetchedViaSPDY(info.was_fetched_via_spdy); + response->setWasNpnNegotiated(info.was_npn_negotiated); + response->setWasAlternateProtocolAvailable( + info.was_alternate_protocol_available); + response->setWasFetchedViaProxy(info.was_fetched_via_proxy); + response->setConnectionID(info.connection_id); + response->setConnectionReused(info.connection_reused); + + WebURLLoadTiming timing; + timing.initialize(); + const ResourceLoaderBridge::LoadTimingInfo& timing_info = info.load_timing; + timing.setRequestTime(timing_info.base_time.ToDoubleT()); + timing.setProxyStart(timing_info.proxy_start); + timing.setProxyEnd(timing_info.proxy_end); + timing.setDNSStart(timing_info.dns_start); + timing.setDNSEnd(timing_info.dns_end); + timing.setConnectStart(timing_info.connect_start); + timing.setConnectEnd(timing_info.connect_end); + timing.setSSLStart(timing_info.ssl_start); + timing.setSSLEnd(timing_info.ssl_end); + timing.setSendStart(timing_info.send_start); + timing.setSendEnd(timing_info.send_end); + timing.setReceiveHeadersEnd(timing_info.receive_headers_end); + response->setLoadTiming(timing); + + const net::HttpResponseHeaders* headers = info.headers; + if (!headers) + return; + + response->setHTTPStatusCode(headers->response_code()); + response->setHTTPStatusText(WebString::fromUTF8(headers->GetStatusText())); + + // TODO(darin): We should leverage HttpResponseHeaders for this, and this + // should be using the same code as ResourceDispatcherHost. + // TODO(jungshik): Figure out the actual value of the referrer charset and + // pass it to GetSuggestedFilename. + std::string value; + if (headers->EnumerateHeader(NULL, "content-disposition", &value)) { + response->setSuggestedFileName(webkit_glue::FilePathToWebString( + net::GetSuggestedFilename(url, value, "", FilePath()))); + } + + Time time_val; + if (headers->GetLastModifiedValue(&time_val)) + response->setLastModifiedDate(time_val.ToDoubleT()); + + // Build up the header map. + void* iter = NULL; + std::string name; + while (headers->EnumerateHeaderLines(&iter, &name, &value)) { + response->addHTTPHeaderField(WebString::fromUTF8(name), + WebString::fromUTF8(value)); + } +} + +} // namespace + +// WebURLLoaderImpl::Context -------------------------------------------------- + +// This inner class exists since the WebURLLoader may be deleted while inside a +// call to WebURLLoaderClient. The bridge requires its Peer to stay alive +// until it receives OnCompletedRequest. +class WebURLLoaderImpl::Context : public base::RefCounted<Context>, + public ResourceLoaderBridge::Peer { + public: + explicit Context(WebURLLoaderImpl* loader); + + WebURLLoaderClient* client() const { return client_; } + void set_client(WebURLLoaderClient* client) { client_ = client; } + + void Cancel(); + void SetDefersLoading(bool value); + void Start( + const WebURLRequest& request, + ResourceLoaderBridge::SyncLoadResponse* sync_load_response); + + // ResourceLoaderBridge::Peer methods: + virtual void OnUploadProgress(uint64 position, uint64 size); + virtual bool OnReceivedRedirect( + const GURL& new_url, + const ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies); + virtual void OnReceivedResponse( + const ResourceLoaderBridge::ResponseInfo& info, bool content_filtered); + virtual void OnReceivedData(const char* data, int len); + virtual void OnReceivedCachedMetadata(const char* data, int len); + virtual void OnCompletedRequest( + const URLRequestStatus& status, const std::string& security_info); + virtual GURL GetURLForDebugging() const; + + private: + friend class base::RefCounted<Context>; + ~Context() {} + + void HandleDataURL(); + + WebURLLoaderImpl* loader_; + WebURLRequest request_; + WebURLLoaderClient* client_; + scoped_ptr<ResourceLoaderBridge> bridge_; + scoped_ptr<FtpDirectoryListingResponseDelegate> ftp_listing_delegate_; + scoped_ptr<MultipartResponseDelegate> multipart_delegate_; + + // TODO(japhet): Storing this is a temporary hack for site isolation logging. + WebURL response_url_; +}; + +WebURLLoaderImpl::Context::Context(WebURLLoaderImpl* loader) + : loader_(loader), + client_(NULL) { +} + +void WebURLLoaderImpl::Context::Cancel() { + // The bridge will still send OnCompletedRequest, which will Release() us, so + // we don't do that here. + if (bridge_.get()) + bridge_->Cancel(); + + // Ensure that we do not notify the multipart delegate anymore as it has + // its own pointer to the client. + if (multipart_delegate_.get()) + multipart_delegate_->Cancel(); + + // Do not make any further calls to the client. + client_ = NULL; + loader_ = NULL; +} + +void WebURLLoaderImpl::Context::SetDefersLoading(bool value) { + if (bridge_.get()) + bridge_->SetDefersLoading(value); +} + +void WebURLLoaderImpl::Context::Start( + const WebURLRequest& request, + ResourceLoaderBridge::SyncLoadResponse* sync_load_response) { + DCHECK(!bridge_.get()); + + request_ = request; // Save the request. + + GURL url = request.url(); + if (url.SchemeIs("data")) { + if (sync_load_response) { + // This is a sync load. Do the work now. + sync_load_response->url = url; + std::string data; + GetInfoFromDataURL(sync_load_response->url, sync_load_response, + &sync_load_response->data, + &sync_load_response->status); + } else { + AddRef(); // Balanced in OnCompletedRequest + MessageLoop::current()->PostTask(FROM_HERE, + NewRunnableMethod(this, &Context::HandleDataURL)); + } + return; + } + + GURL referrer_url( + request.httpHeaderField(WebString::fromUTF8("Referer")).utf8()); + const std::string& method = request.httpMethod().utf8(); + + int load_flags = net::LOAD_NORMAL; + switch (request.cachePolicy()) { + case WebURLRequest::ReloadIgnoringCacheData: + // Required by LayoutTests/http/tests/misc/refresh-headers.php + load_flags |= net::LOAD_VALIDATE_CACHE; + break; + case WebURLRequest::ReturnCacheDataElseLoad: + load_flags |= net::LOAD_PREFERRING_CACHE; + break; + case WebURLRequest::ReturnCacheDataDontLoad: + load_flags |= net::LOAD_ONLY_FROM_CACHE; + break; + case WebURLRequest::UseProtocolCachePolicy: + break; + } + + if (request.reportUploadProgress()) + load_flags |= net::LOAD_ENABLE_UPLOAD_PROGRESS; + if (request.reportLoadTiming()) + load_flags |= net::LOAD_ENABLE_LOAD_TIMING; + + if (!request.allowCookies() || !request.allowStoredCredentials()) { + load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; + load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; + } + + if (!request.allowStoredCredentials()) + load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA; + + + // TODO(jcampan): in the non out-of-process plugin case the request does not + // have a requestor_pid. Find a better place to set this. + int requestor_pid = request.requestorProcessID(); + if (requestor_pid == 0) + requestor_pid = base::GetCurrentProcId(); + + HeaderFlattener flattener(load_flags); + request.visitHTTPHeaderFields(&flattener); + + // TODO(abarth): These are wrong! I need to figure out how to get the right + // strings here. See: http://crbug.com/8706 + std::string frame_origin = request.firstPartyForCookies().spec(); + std::string main_frame_origin = request.firstPartyForCookies().spec(); + + // TODO(brettw) this should take parameter encoding into account when + // creating the GURLs. + + webkit_glue::ResourceLoaderBridge::RequestInfo request_info; + request_info.method = method; + request_info.url = url; + request_info.first_party_for_cookies = request.firstPartyForCookies(); + request_info.referrer = referrer_url; + request_info.frame_origin = frame_origin; + request_info.main_frame_origin = main_frame_origin; + request_info.headers = flattener.GetBuffer(); + request_info.load_flags = load_flags; + request_info.requestor_pid = requestor_pid; + request_info.request_type = FromTargetType(request.targetType()); + request_info.appcache_host_id = request.appCacheHostID(); + request_info.routing_id = request.requestorID(); + bridge_.reset(ResourceLoaderBridge::Create(request_info)); + + if (!request.httpBody().isNull()) { + // GET and HEAD requests shouldn't have http bodies. + DCHECK(method != "GET" && method != "HEAD"); + const WebHTTPBody& httpBody = request.httpBody(); + size_t i = 0; + WebHTTPBody::Element element; + while (httpBody.elementAt(i++, element)) { + switch (element.type) { + case WebHTTPBody::Element::TypeData: + if (!element.data.isEmpty()) { + // WebKit sometimes gives up empty data to append. These aren't + // necessary so we just optimize those out here. + bridge_->AppendDataToUpload( + element.data.data(), static_cast<int>(element.data.size())); + } + break; + case WebHTTPBody::Element::TypeFile: + if (element.fileLength == -1) { + bridge_->AppendFileToUpload( + WebStringToFilePath(element.filePath)); + } else { + bridge_->AppendFileRangeToUpload( + WebStringToFilePath(element.filePath), + static_cast<uint64>(element.fileStart), + static_cast<uint64>(element.fileLength), + base::Time::FromDoubleT(element.fileInfo.modificationTime)); + } + break; + default: + NOTREACHED(); + } + } + bridge_->SetUploadIdentifier(request.httpBody().identifier()); + } + + if (sync_load_response) { + bridge_->SyncLoad(sync_load_response); + return; + } + + if (bridge_->Start(this)) { + AddRef(); // Balanced in OnCompletedRequest + } else { + bridge_.reset(); + } +} + +void WebURLLoaderImpl::Context::OnUploadProgress(uint64 position, uint64 size) { + if (client_) + client_->didSendData(loader_, position, size); +} + +bool WebURLLoaderImpl::Context::OnReceivedRedirect( + const GURL& new_url, + const ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + if (!client_) + return false; + + WebURLResponse response; + response.initialize(); + PopulateURLResponse(request_.url(), info, &response); + + // TODO(darin): We lack sufficient information to construct the actual + // request that resulted from the redirect. + WebURLRequest new_request(new_url); + new_request.setFirstPartyForCookies(request_.firstPartyForCookies()); + + WebString referrer_string = WebString::fromUTF8("Referer"); + WebString referrer = request_.httpHeaderField(referrer_string); + if (!WebSecurityPolicy::shouldHideReferrer(new_url, referrer)) + new_request.setHTTPHeaderField(referrer_string, referrer); + + if (response.httpStatusCode() == 307) + new_request.setHTTPMethod(request_.httpMethod()); + + client_->willSendRequest(loader_, new_request, response); + request_ = new_request; + *has_new_first_party_for_cookies = true; + *new_first_party_for_cookies = request_.firstPartyForCookies(); + + // Only follow the redirect if WebKit left the URL unmodified. + if (new_url == GURL(new_request.url())) + return true; + + // We assume that WebKit only changes the URL to suppress a redirect, and we + // assume that it does so by setting it to be invalid. + DCHECK(!new_request.url().isValid()); + return false; +} + +void WebURLLoaderImpl::Context::OnReceivedResponse( + const ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + if (!client_) + return; + + WebURLResponse response; + response.initialize(); + PopulateURLResponse(request_.url(), info, &response); + response.setIsContentFiltered(content_filtered); + + bool show_raw_listing = (GURL(request_.url()).query() == "raw"); + + if (info.mime_type == "text/vnd.chromium.ftp-dir") { + if (show_raw_listing) { + // Set the MIME type to plain text to prevent any active content. + response.setMIMEType("text/plain"); + } else { + // We're going to produce a parsed listing in HTML. + response.setMIMEType("text/html"); + } + } + + client_->didReceiveResponse(loader_, response); + + // We may have been cancelled after didReceiveResponse, which would leave us + // without a client and therefore without much need to do further handling. + if (!client_) + return; + + DCHECK(!ftp_listing_delegate_.get()); + DCHECK(!multipart_delegate_.get()); + if (info.headers && info.mime_type == "multipart/x-mixed-replace") { + std::string content_type; + info.headers->EnumerateHeader(NULL, "content-type", &content_type); + + std::string boundary = net::GetHeaderParamValue(content_type, "boundary"); + TrimString(boundary, " \"", &boundary); + + // If there's no boundary, just handle the request normally. In the gecko + // code, nsMultiMixedConv::OnStartRequest throws an exception. + if (!boundary.empty()) { + multipart_delegate_.reset( + new MultipartResponseDelegate(client_, loader_, response, boundary)); + } + } else if (info.mime_type == "text/vnd.chromium.ftp-dir" && + !show_raw_listing) { + ftp_listing_delegate_.reset( + new FtpDirectoryListingResponseDelegate(client_, loader_, response)); + } + + response_url_ = response.url(); +} + +void WebURLLoaderImpl::Context::OnReceivedData(const char* data, int len) { + if (!client_) + return; + + // Temporary logging, see site_isolation_metrics.h/cc. + SiteIsolationMetrics::SniffCrossOriginHTML(response_url_, data, len); + + if (ftp_listing_delegate_.get()) { + // The FTP listing delegate will make the appropriate calls to + // client_->didReceiveData and client_->didReceiveResponse. + ftp_listing_delegate_->OnReceivedData(data, len); + } else if (multipart_delegate_.get()) { + // The multipart delegate will make the appropriate calls to + // client_->didReceiveData and client_->didReceiveResponse. + multipart_delegate_->OnReceivedData(data, len); + } else { + client_->didReceiveData(loader_, data, len); + } +} + +void WebURLLoaderImpl::Context::OnReceivedCachedMetadata( + const char* data, int len) { + if (client_) + client_->didReceiveCachedMetadata(loader_, data, len); +} + +void WebURLLoaderImpl::Context::OnCompletedRequest( + const URLRequestStatus& status, + const std::string& security_info) { + if (ftp_listing_delegate_.get()) { + ftp_listing_delegate_->OnCompletedRequest(); + ftp_listing_delegate_.reset(NULL); + } else if (multipart_delegate_.get()) { + multipart_delegate_->OnCompletedRequest(); + multipart_delegate_.reset(NULL); + } + + // Prevent any further IPC to the browser now that we're complete. + bridge_.reset(); + + if (client_) { + if (status.status() != URLRequestStatus::SUCCESS) { + int error_code; + if (status.status() == URLRequestStatus::HANDLED_EXTERNALLY) { + // By marking this request as aborted we insure that we don't navigate + // to an error page. + error_code = net::ERR_ABORTED; + } else { + error_code = status.os_error(); + } + WebURLError error; + error.domain = WebString::fromUTF8(net::kErrorDomain); + error.reason = error_code; + error.unreachableURL = request_.url(); + client_->didFail(loader_, error); + } else { + client_->didFinishLoading(loader_); + } + } + + // Temporary logging, see site_isolation_metrics.h/cc + SiteIsolationMetrics::RemoveCompletedResponse(response_url_); + + // We are done with the bridge now, and so we need to release the reference + // to ourselves that we took on behalf of the bridge. This may cause our + // destruction. + Release(); +} + +GURL WebURLLoaderImpl::Context::GetURLForDebugging() const { + return request_.url(); +} + +void WebURLLoaderImpl::Context::HandleDataURL() { + ResourceLoaderBridge::ResponseInfo info; + URLRequestStatus status; + std::string data; + + if (GetInfoFromDataURL(request_.url(), &info, &data, &status)) { + OnReceivedResponse(info, false); + if (!data.empty()) + OnReceivedData(data.data(), data.size()); + } + + OnCompletedRequest(status, info.security_info); +} + +// WebURLLoaderImpl ----------------------------------------------------------- + +WebURLLoaderImpl::WebURLLoaderImpl() + : ALLOW_THIS_IN_INITIALIZER_LIST(context_(new Context(this))) { +} + +WebURLLoaderImpl::~WebURLLoaderImpl() { + cancel(); +} + +void WebURLLoaderImpl::loadSynchronously(const WebURLRequest& request, + WebURLResponse& response, + WebURLError& error, + WebData& data) { + ResourceLoaderBridge::SyncLoadResponse sync_load_response; + context_->Start(request, &sync_load_response); + + const GURL& final_url = sync_load_response.url; + + // TODO(tc): For file loads, we may want to include a more descriptive + // status code or status text. + const URLRequestStatus::Status& status = sync_load_response.status.status(); + if (status != URLRequestStatus::SUCCESS && + status != URLRequestStatus::HANDLED_EXTERNALLY) { + response.setURL(final_url); + error.domain = WebString::fromUTF8(net::kErrorDomain); + error.reason = sync_load_response.status.os_error(); + error.unreachableURL = final_url; + return; + } + + PopulateURLResponse(final_url, sync_load_response, &response); + + data.assign(sync_load_response.data.data(), + sync_load_response.data.size()); +} + +void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request, + WebURLLoaderClient* client) { + DCHECK(!context_->client()); + + context_->set_client(client); + context_->Start(request, NULL); +} + +void WebURLLoaderImpl::cancel() { + context_->Cancel(); +} + +void WebURLLoaderImpl::setDefersLoading(bool value) { + context_->SetDefersLoading(value); +} + +} // namespace webkit_glue diff --git a/webkit/glue/weburlloader_impl.h b/webkit/glue/weburlloader_impl.h new file mode 100644 index 0000000..f9e5a0b --- /dev/null +++ b/webkit/glue/weburlloader_impl.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. Use of this +// source code is governed by a BSD-style license that can be found in the +// LICENSE file. + +#ifndef WEBKIT_GLUE_WEBURLLOADER_IMPL_H_ +#define WEBKIT_GLUE_WEBURLLOADER_IMPL_H_ + +#include "base/ref_counted.h" +#include "third_party/WebKit/WebKit/chromium/public/WebURLLoader.h" + +namespace webkit_glue { + +class WebURLLoaderImpl : public WebKit::WebURLLoader { + public: + WebURLLoaderImpl(); + ~WebURLLoaderImpl(); + + // WebURLLoader methods: + virtual void loadSynchronously( + const WebKit::WebURLRequest& request, + WebKit::WebURLResponse& response, + WebKit::WebURLError& error, + WebKit::WebData& data); + virtual void loadAsynchronously( + const WebKit::WebURLRequest& request, + WebKit::WebURLLoaderClient* client); + virtual void cancel(); + virtual void setDefersLoading(bool value); + + private: + class Context; + scoped_refptr<Context> context_; +}; + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_WEBURLLOADER_IMPL_H_ diff --git a/webkit/glue/webview_unittest.cc b/webkit/glue/webview_unittest.cc new file mode 100644 index 0000000..22c9cea --- /dev/null +++ b/webkit/glue/webview_unittest.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2006-2008 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 "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/WebKit/chromium/public/WebView.h" +#include "webkit/tools/test_shell/test_shell_test.h" + +using WebKit::WebView; + +class WebViewTest : public TestShellTest { +}; + +TEST_F(WebViewTest, ActiveState) { + WebView* view = test_shell_->webView(); + ASSERT_TRUE(view != 0); + + view->setIsActive(true); + EXPECT_TRUE(view->isActive()); + + view->setIsActive(false); + EXPECT_FALSE(view->isActive()); + + view->setIsActive(true); + EXPECT_TRUE(view->isActive()); +} + +// TODO(viettrungluu): add more tests diff --git a/webkit/glue/window_open_disposition.cc b/webkit/glue/window_open_disposition.cc new file mode 100644 index 0000000..6a117d3 --- /dev/null +++ b/webkit/glue/window_open_disposition.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "webkit/glue/window_open_disposition.h" + +#include "base/logging.h" + +WindowOpenDisposition NavigationPolicyToDisposition( + WebKit::WebNavigationPolicy policy) { + switch (policy) { + case WebKit::WebNavigationPolicyIgnore: + return IGNORE_ACTION; + case WebKit::WebNavigationPolicyDownload: + return SAVE_TO_DISK; + case WebKit::WebNavigationPolicyCurrentTab: + return CURRENT_TAB; + case WebKit::WebNavigationPolicyNewBackgroundTab: + return NEW_BACKGROUND_TAB; + case WebKit::WebNavigationPolicyNewForegroundTab: + return NEW_FOREGROUND_TAB; + case WebKit::WebNavigationPolicyNewWindow: + return NEW_WINDOW; + case WebKit::WebNavigationPolicyNewPopup: + return NEW_POPUP; + default: + NOTREACHED() << "Unexpected WebNavigationPolicy"; + return IGNORE_ACTION; + } +} diff --git a/webkit/glue/window_open_disposition.h b/webkit/glue/window_open_disposition.h new file mode 100644 index 0000000..24495f1 --- /dev/null +++ b/webkit/glue/window_open_disposition.h @@ -0,0 +1,28 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_WINDOW_OPEN_DISPOSITION_H_ +#define WEBKIT_GLUE_WINDOW_OPEN_DISPOSITION_H_ + +#include "third_party/WebKit/WebKit/chromium/public/WebNavigationPolicy.h" + +enum WindowOpenDisposition { + SUPPRESS_OPEN, + CURRENT_TAB, + // Indicates that only one tab with the url should exist in the same window. + SINGLETON_TAB, + NEW_FOREGROUND_TAB, + NEW_BACKGROUND_TAB, + NEW_POPUP, + NEW_WINDOW, + SAVE_TO_DISK, + OFF_THE_RECORD, + IGNORE_ACTION +}; + +// Conversion function: +WindowOpenDisposition NavigationPolicyToDisposition( + WebKit::WebNavigationPolicy policy); + +#endif // WEBKIT_GLUE_WINDOW_OPEN_DISPOSITION_H_ |