summaryrefslogtreecommitdiffstats
path: root/webkit/glue/plugins/webplugin_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/glue/plugins/webplugin_impl.cc')
-rw-r--r--webkit/glue/plugins/webplugin_impl.cc1305
1 files changed, 1305 insertions, 0 deletions
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