// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/renderer/npapi/webplugin_impl.h" #include "base/bind.h" #include "base/command_line.h" #include "base/debug/crash_logging.h" #include "base/logging.h" #include "base/memory/linked_ptr.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "cc/layers/io_surface_layer.h" #include "content/child/appcache/web_application_cache_host_impl.h" #include "content/child/npapi/plugin_host.h" #include "content/child/npapi/plugin_instance.h" #include "content/child/npapi/webplugin_delegate_impl.h" #include "content/child/npapi/webplugin_resource_client.h" #include "content/common/view_messages.h" #include "content/public/common/content_constants.h" #include "content/public/common/content_switches.h" #include "content/public/renderer/content_renderer_client.h" #include "content/renderer/npapi/webplugin_delegate_proxy.h" #include "content/renderer/render_frame_impl.h" #include "content/renderer/render_process.h" #include "content/renderer/render_thread_impl.h" #include "content/renderer/render_view_impl.h" #include "net/base/escape.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "skia/ext/platform_canvas.h" #include "third_party/WebKit/public/platform/WebCString.h" #include "third_party/WebKit/public/platform/WebCookieJar.h" #include "third_party/WebKit/public/platform/WebCursorInfo.h" #include "third_party/WebKit/public/platform/WebData.h" #include "third_party/WebKit/public/platform/WebHTTPBody.h" #include "third_party/WebKit/public/platform/WebHTTPHeaderVisitor.h" #include "third_party/WebKit/public/platform/WebURL.h" #include "third_party/WebKit/public/platform/WebURLError.h" #include "third_party/WebKit/public/platform/WebURLLoader.h" #include "third_party/WebKit/public/platform/WebURLLoaderClient.h" #include "third_party/WebKit/public/platform/WebURLResponse.h" #include "third_party/WebKit/public/web/WebConsoleMessage.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebPluginContainer.h" #include "third_party/WebKit/public/web/WebPluginParams.h" #include "third_party/WebKit/public/web/WebURLLoaderOptions.h" #include "third_party/WebKit/public/web/WebView.h" #include "ui/gfx/rect.h" #include "url/gurl.h" #include "url/url_util.h" #include "webkit/child/multipart_response_delegate.h" #include "webkit/renderer/compositor_bindings/web_layer_impl.h" using blink::WebCanvas; using blink::WebConsoleMessage; using blink::WebCookieJar; using blink::WebCString; using blink::WebCursorInfo; using blink::WebData; using blink::WebDataSource; using blink::WebFrame; using blink::WebHTTPBody; using blink::WebHTTPHeaderVisitor; using blink::WebInputEvent; using blink::WebKeyboardEvent; using blink::WebMouseEvent; using blink::WebPluginContainer; using blink::WebPluginParams; using blink::WebRect; using blink::WebString; using blink::WebURL; using blink::WebURLError; using blink::WebURLLoader; using blink::WebURLLoaderClient; using blink::WebURLLoaderOptions; using blink::WebURLRequest; using blink::WebURLResponse; using blink::WebVector; using blink::WebView; using webkit_glue::MultipartResponseDelegate; namespace content { 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) : byte_range_lower_bound_(0), resource_client_(resource_client) {} 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) { int64 byte_range_upper_bound, instance_size; if (!MultipartResponseDelegate::ReadContentRanges( response, &byte_range_lower_bound_, &byte_range_upper_bound, &instance_size)) { NOTREACHED(); } } // Receives individual part data from a multipart response. virtual void didReceiveData(WebURLLoader*, const char* data, int data_length, int encoded_data_length) { // 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_length, byte_range_lower_bound_); byte_range_lower_bound_ += data_length; } virtual void didFinishLoading(WebURLLoader*, double finishTime) {} virtual void didFail(WebURLLoader*, const WebURLError&) {} private: // The lower bound of the byte range. int64 byte_range_lower_bound_; // The handler for the data. WebPluginResourceClient* resource_client_; }; class HeaderFlattener : public WebHTTPHeaderVisitor { public: explicit 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 = base::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 // blink::WebPlugin ---------------------------------------------------------- struct WebPluginImpl::ClientInfo { unsigned long id; WebPluginResourceClient* client; blink::WebURLRequest request; bool pending_failure_notification; linked_ptr<blink::WebURLLoader> loader; bool notify_redirects; bool is_plugin_src_load; int64 data_offset; }; bool WebPluginImpl::initialize(WebPluginContainer* container) { if (!render_view_.get()) { LOG(ERROR) << "No RenderView"; return false; } WebPluginDelegate* plugin_delegate = CreatePluginDelegate(); if (!plugin_delegate) return false; // Store the plugin's unique identifier, used by the container to track its // script objects. npp_ = plugin_delegate->GetPluginNPP(); // Set the container before Initialize because the plugin may // synchronously call NPN_GetValue to get its container, or make calls // passing script objects that need to be tracked, during initialization. SetContainer(container); bool ok = plugin_delegate->Initialize( plugin_url_, arg_names_, arg_values_, load_manually_); if (!ok) { plugin_delegate->PluginDestroyed(); blink::WebPlugin* replacement_plugin = GetContentClient()->renderer()->CreatePluginReplacement( render_frame_, file_path_); if (!replacement_plugin) return false; // Disable scripting by this plugin before replacing it with the new // one. This plugin also needs destroying, so use destroy(), which will // implicitly disable scripting while un-setting the container. destroy(); // Inform the container of the replacement plugin, then initialize it. container->setPlugin(replacement_plugin); return replacement_plugin->initialize(container); } delegate_ = plugin_delegate; return true; } void WebPluginImpl::destroy() { SetContainer(NULL); base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); } NPObject* WebPluginImpl::scriptableObject() { if (!delegate_) return NULL; return delegate_->GetPluginScriptableObject(); } NPP WebPluginImpl::pluginNPP() { return npp_; } bool WebPluginImpl::getFormValue(blink::WebString& value) { if (!delegate_) return false; base::string16 form_value; if (!delegate_->GetFormValue(&form_value)) return false; value = form_value; return true; } 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_ && render_view_.get() && (first_geometry_update_ || !new_geometry.Equals(geometry_))) { render_view_->SchedulePluginMove(new_geometry); // We invalidate windowed plugins during the first geometry update to // ensure that they get reparented to the wrapper window in the browser. // This ensures that they become visible and are painted by the OS. This is // required as some pages don't invalidate when the plugin is added. if (first_geometry_update_ && window_) { InvalidateRect(window_rect); } } // Only UpdateGeometry if either the window or clip rects have changed. if (delegate_ && (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. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&WebPluginImpl::OnDownloadPluginSrcUrl, weak_factory_.GetWeakPtr())); } } #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_ || !render_view_.get()) return; WebPluginGeometry move; move.window = window_; move.window_rect = gfx::Rect(); move.clip_rect = gfx::Rect(); move.rects_valid = false; move.visible = visible; render_view_->SchedulePluginMove(move); } bool WebPluginImpl::acceptsInputEvents() { return accepts_input_events_; } bool WebPluginImpl::handleInputEvent( const WebInputEvent& event, WebCursorInfo& cursor_info) { // Swallow context menu events in order to suppress the default context menu. if (event.type == WebInputEvent::ContextMenu) return true; WebCursor::CursorInfo web_cursor_info; bool ret = delegate_->HandleInputEvent(event, &web_cursor_info); cursor_info.type = web_cursor_info.type; cursor_info.hotSpot = web_cursor_info.hotspot; cursor_info.customImage = web_cursor_info.custom_image; cursor_info.imageScaleFactor = web_cursor_info.image_scale_factor; #if defined(OS_WIN) cursor_info.externalHandle = web_cursor_info.external_handle; #endif return ret; } 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::isPlaceholder() { return false; } // ----------------------------------------------------------------------------- WebPluginImpl::WebPluginImpl( WebFrame* webframe, const WebPluginParams& params, const base::FilePath& file_path, const base::WeakPtr<RenderViewImpl>& render_view, RenderFrameImpl* render_frame) : windowless_(false), window_(gfx::kNullPluginWindow), accepts_input_events_(false), render_frame_(render_frame), render_view_(render_view), webframe_(webframe), delegate_(NULL), container_(NULL), npp_(NULL), plugin_url_(params.url), load_manually_(params.loadManually), first_geometry_update_(true), ignore_response_error_(false), file_path_(file_path), mime_type_(UTF16ToASCII(params.mimeType)), weak_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()); } // Set subresource URL for crash reporting. base::debug::SetCrashKeyValue("subresource_url", plugin_url_.spec()); } WebPluginImpl::~WebPluginImpl() { } void WebPluginImpl::SetWindow(gfx::PluginWindowHandle window) { if (window) { DCHECK(!windowless_); window_ = window; #if defined(OS_MACOSX) // TODO(kbr): remove. http://crbug.com/105344 // 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 accepts_input_events_ = false; #if defined(USE_X11) // Tell the view delegate that the plugin window was created, so that it // can create necessary container widgets. render_view_->Send(new ViewHostMsg_CreatePluginContainer( render_view_->routing_id(), window)); #endif // USE_X11 #endif // OS_MACOSX } else { DCHECK(!window_); // Make sure not called twice. windowless_ = true; accepts_input_events_ = true; } } void WebPluginImpl::SetAcceptsInputEvents(bool accepts) { accepts_input_events_ = accepts; } void WebPluginImpl::WillDestroyWindow(gfx::PluginWindowHandle window) { DCHECK_EQ(window, window_); window_ = gfx::kNullPluginWindow; if (render_view_.get()) { #if defined(USE_X11) render_view_->Send(new ViewHostMsg_DestroyPluginContainer( render_view_->routing_id(), window)); #endif render_view_->CleanupWindowInPluginMoves(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 = 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_ == kFlashPluginSwfMimeType && 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; } WebPluginDelegate* WebPluginImpl::CreatePluginDelegate() { bool in_process_plugin = RenderProcess::current()->UseInProcessPlugins(); if (in_process_plugin) { #if defined(OS_WIN) && !defined(USE_AURA) return WebPluginDelegateImpl::Create(this, file_path_, mime_type_); #else // In-proc plugins aren't supported on non-Windows. NOTIMPLEMENTED(); return NULL; #endif } return new WebPluginDelegateProxy( this, mime_type_, render_view_, render_frame_); } WebPluginImpl::RoutingStatus WebPluginImpl::RouteToFrame( const char* url, bool is_javascript_url, bool popups_allowed, 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.SchemeIsHTTPOrHTTPS()) return INVALID_URL; } WebURLRequest request(complete_url); SetReferrer(&request, referrer_flag); request.setHTTPMethod(WebString::fromUTF8(method)); request.setFirstPartyForCookies( webframe_->document().firstPartyForCookies()); request.setHasUserGesture(popups_allowed); 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(); } bool WebPluginImpl::FindProxyForUrl(const GURL& url, std::string* proxy_list) { // Proxy resolving doesn't work in single-process mode. return false; } void WebPluginImpl::SetCookie(const GURL& url, const GURL& first_party_for_cookies, const std::string& cookie) { if (!render_view_.get()) return; WebCookieJar* cookie_jar = render_view_->cookie_jar(); 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 (!render_view_.get()) return std::string(); WebCookieJar* cookie_jar = render_view_->cookie_jar(); if (!cookie_jar) { DLOG(WARNING) << "No cookie jar!"; return std::string(); } return UTF16ToUTF8(cookie_jar->cookies(url, first_party_for_cookies)); } void WebPluginImpl::URLRedirectResponse(bool allow, int resource_id) { for (size_t i = 0; i < clients_.size(); ++i) { if (clients_[i].id == static_cast<unsigned long>(resource_id)) { if (clients_[i].loader.get()) { if (allow) { clients_[i].loader->setDefersLoading(false); } else { clients_[i].loader->cancel(); if (clients_[i].client) clients_[i].client->DidFail(clients_[i].id); } } break; } } } bool WebPluginImpl::CheckIfRunInsecureContent(const GURL& url) { if (!webframe_) return true; return webframe_->checkIfRunInsecureContent(url); } #if defined(OS_MACOSX) WebPluginAcceleratedSurface* WebPluginImpl::GetAcceleratedSurface( gfx::GpuPreference gpu_preference) { return NULL; } void WebPluginImpl::AcceleratedPluginEnabledRendering() { } void WebPluginImpl::AcceleratedPluginAllocatedIOSurface(int32 width, int32 height, uint32 surface_id) { next_io_surface_allocated_ = true; next_io_surface_width_ = width; next_io_surface_height_ = height; next_io_surface_id_ = surface_id; } void WebPluginImpl::AcceleratedPluginSwappedIOSurface() { if (!container_) return; // Deferring the call to setBackingIOSurfaceId is an attempt to // work around garbage occasionally showing up in the plugin's // area during live resizing of Core Animation plugins. The // assumption was that by the time this was called, the plugin // process would have populated the newly allocated IOSurface. It // is not 100% clear at this point why any garbage is getting // through. More investigation is needed. http://crbug.com/105346 if (next_io_surface_allocated_) { if (next_io_surface_id_) { if (!io_surface_layer_.get()) { io_surface_layer_ = cc::IOSurfaceLayer::Create(); web_layer_.reset(new webkit::WebLayerImpl(io_surface_layer_)); container_->setWebLayer(web_layer_.get()); } io_surface_layer_->SetIOSurfaceProperties( next_io_surface_id_, gfx::Size(next_io_surface_width_, next_io_surface_height_)); } else { container_->setWebLayer(NULL); web_layer_.reset(); io_surface_layer_ = NULL; } next_io_surface_allocated_ = false; } else { if (io_surface_layer_.get()) io_surface_layer_->SetNeedsDisplay(); } } #endif 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, false, true); } 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& response) { // TODO(jam): THIS LOGIC IS COPIED IN PluginURLFetcher::OnReceivedRedirect // until kDirectNPAPIRequests is the default and we can remove this old path. WebPluginImpl::ClientInfo* client_info = GetClientInfoFromLoader(loader); if (client_info) { // Currently this check is just to catch an https -> http redirect when // loading the main plugin src URL. Longer term, we could investigate // firing mixed diplay or scripting issues for subresource loads // initiated by plug-ins. if (client_info->is_plugin_src_load && webframe_ && !webframe_->checkIfRunInsecureContent(request.url())) { loader->cancel(); client_info->client->DidFail(client_info->id); return; } if (net::HttpResponseHeaders::IsRedirectResponseCode( response.httpStatusCode())) { // If the plugin does not participate in url redirect notifications then // just block cross origin 307 POST redirects. if (!client_info->notify_redirects) { if (response.httpStatusCode() == 307 && LowerCaseEqualsASCII(request.httpMethod().utf8(), "post")) { GURL original_request_url(response.url()); GURL response_url(request.url()); if (original_request_url.GetOrigin() != response_url.GetOrigin()) { loader->setDefersLoading(true); loader->cancel(); client_info->client->DidFail(client_info->id); return; } } } else { loader->setDefersLoading(true); } } client_info->client->WillSendRequest(request.url(), response.httpStatusCode()); } } 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) { // TODO(jam): THIS LOGIC IS COPIED IN PluginURLFetcher::OnReceivedResponse // until kDirectNPAPIRequests is the default and we can remove this old path. static const int kHttpPartialResponseStatusCode = 206; static const int kHttpResponseSuccessStatusCode = 200; WebPluginResourceClient* client = GetClientFromLoader(loader); if (!client) return; ResponseInfo response_info; GetResponseInfo(response, &response_info); ClientInfo* client_info = GetClientInfoFromLoader(loader); if (!client_info) return; bool request_is_seekable = true; if (client->IsMultiByteResponseExpected()) { if (response.httpStatusCode() == kHttpPartialResponseStatusCode) { ClientInfo* client_info = GetClientInfoFromLoader(loader); if (!client_info) return; if (HandleHttpMultipartResponse(response, client)) { // Multiple ranges requested, data will be delivered by // MultipartResponseDelegate. client_info->data_offset = 0; return; } int64 upper_bound = 0, instance_size = 0; // Single range requested - go through original processing for // non-multipart requests, but update data offset. MultipartResponseDelegate::ReadContentRanges(response, &client_info->data_offset, &upper_bound, &instance_size); } else if (response.httpStatusCode() == kHttpResponseSuccessStatusCode) { RenderThreadImpl::current()->RecordAction( UserMetricsAction("Plugin_200ForByteRange")); // 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); // 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 data_length, int encoded_data_length) { WebPluginResourceClient* client = GetClientFromLoader(loader); if (!client) return; 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, data_length, encoded_data_length); } else { loader->setDefersLoading(true); ClientInfo* client_info = GetClientInfoFromLoader(loader); client->DidReceiveData(buffer, data_length, client_info->data_offset); client_info->data_offset += data_length; } } void WebPluginImpl::didFinishLoading(WebURLLoader* loader, double finishTime) { 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); DidStopLoading(); } 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(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(client_info->id); } } 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; if (container_) container_->allowScriptObjects(); } void WebPluginImpl::HandleURLRequest(const char* url, const char* method, const char* target, const char* buf, unsigned int len, int notify_id, bool popups_allowed, bool notify_redirects) { // 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, notify_redirects, false); } 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, bool notify_redirects, bool is_plugin_src_load) { // 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 = url_util::FindAndCompareScheme( url, strlen(url), "javascript", NULL); RoutingStatus routing_status = RouteToFrame( url, is_javascript_url, popups_allowed, 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; // 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)) { WebPluginResourceClient* resource_client = delegate_->CreateResourceClient( resource_id, complete_url, notify_id); if (resource_client) resource_client->DidFail(resource_id); 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; if (!CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableDirectNPAPIRequests)) { // We got here either because the plugin called GetURL/PostURL, or because // we're fetching the data for an embed tag. If we're in multi-process mode, // we want to fetch the data in the plugin process as the renderer won't be // able to request any origin when site isolation is in place. So bounce // this request back to the plugin process which will use ResourceDispatcher // to fetch the url. // TODO(jam): any better way of getting this? Can't find a way to get // frame()->loader()->outgoingReferrer() which // WebFrameImpl::setReferrerForRequest does. WebURLRequest request(complete_url); SetReferrer(&request, referrer_flag); GURL referrer( request.httpHeaderField(WebString::fromUTF8("Referer")).utf8()); GURL first_party_for_cookies = webframe_->document().firstPartyForCookies(); delegate_->FetchURL(resource_id, notify_id, complete_url, first_party_for_cookies, method, buf, len, referrer, notify_redirects, is_plugin_src_load, 0, render_view_->routing_id()); } else { WebPluginResourceClient* resource_client = delegate_->CreateResourceClient( resource_id, complete_url, notify_id); if (!resource_client) return; InitiateHTTPRequest(resource_id, resource_client, complete_url, method, buf, len, NULL, referrer_flag, notify_redirects, is_plugin_src_load); } } 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, bool notify_redirects, bool is_plugin_src_load) { 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; info.notify_redirects = notify_redirects; info.is_plugin_src_load = is_plugin_src_load; info.data_offset = 0; 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); WebURLLoaderOptions options; options.allowCredentials = true; options.crossOriginRequestPolicy = WebURLLoaderOptions::CrossOriginRequestPolicyAllow; info.loader.reset(webframe_->createAssociatedURLLoader(options)); 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, false, false); } void WebPluginImpl::DidStartLoading() { if (render_view_.get()) { // TODO(darin): Make is_loading_ be a counter! render_view_->didStartLoading(); } } void WebPluginImpl::DidStopLoading() { if (render_view_.get()) { // TODO(darin): Make is_loading_ be a counter! render_view_->didStopLoading(); } } 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(resource_id); } break; } client_index++; } } bool WebPluginImpl::IsOffTheRecord() { return false; } bool WebPluginImpl::HandleHttpMultipartResponse( const WebURLResponse& response, WebPluginResourceClient* client) { std::string multipart_boundary; if (!MultipartResponseDelegate::ReadMultipartBoundary( response, &multipart_boundary)) { return false; } DidStartLoading(); 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; return true; } 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 = CreatePluginDelegate(); // Store the plugin's unique identifier, used by the container to track its // script objects, and enable script objects (since Initialize may use them // even if it fails). npp_ = plugin_delegate->GetPluginNPP(); container_->allowScriptObjects(); bool ok = plugin_delegate && plugin_delegate->Initialize( plugin_url_, arg_names_, arg_values_, load_manually_); if (!ok) { container_->clearScriptObjects(); 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) { // JavaScript garbage collection may cause plugin script object references to // be retained long after the plugin is destroyed. Some plugins won't cope // with their objects being released after they've been destroyed, and once // we've actually unloaded the plugin the object's releaseobject() code may // no longer be in memory. The container tracks the plugin's objects and lets // us invalidate them, releasing the references to them held by the JavaScript // runtime. if (container_) { container_->clearScriptObjects(); container_->setWebLayer(NULL); } // Call PluginDestroyed() first to prevent the plugin from calling us back // in the middle of tearing down the render tree. if (delegate_) { // The plugin may call into the browser and pass script objects even during // teardown, so temporarily re-enable plugin script objects. DCHECK(container_); container_->allowScriptObjects(); delegate_->PluginDestroyed(); delegate_ = NULL; // Invalidate any script objects created during teardown here, before the // plugin might actually be unloaded. container_->clearScriptObjects(); } // 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; weak_factory_.InvalidateWeakPtrs(); } void WebPluginImpl::SetReferrer(blink::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; } } } // namespace content