diff options
32 files changed, 1310 insertions, 51 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index b12c05e..a465f19 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4524,6 +4524,11 @@ Keep your key file in a safe place. You will need it to create new versions of y Something went wrong when trying to print. Please check your printer and try again. </message> + <!-- Cloud Print dialog messages --> + <message name="IDS_CLOUD_PRINT_TITLE" desc="Title for the print dialog"> + Print + </message> + <!-- Load State --> <message name="IDS_LOAD_STATE_IDLE"></message> <message name="IDS_LOAD_STATE_WAITING_FOR_CACHE"> diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index aee74e5..f375ce0 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -3046,7 +3046,8 @@ void Browser::InitCommandState() { #if defined(OS_CHROMEOS) command_updater_.UpdateCommandEnabled(IDC_COMPACT_NAVBAR, true); - command_updater_.UpdateCommandEnabled(IDC_PRINT, false); + if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableCloudPrint)) + command_updater_.UpdateCommandEnabled(IDC_PRINT, false); #endif ExtensionsService* extensions_service = profile()->GetExtensionsService(); bool enable_extensions = diff --git a/chrome/browser/chrome_plugin_host.cc b/chrome/browser/chrome_plugin_host.cc index c9b0428..30e1fa4 100644 --- a/chrome/browser/chrome_plugin_host.cc +++ b/chrome/browser/chrome_plugin_host.cc @@ -326,6 +326,7 @@ class ModelessHtmlDialogDelegate : public HtmlDialogUIDelegate { io_message_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &ModelessHtmlDialogDelegate::ReportResults, json_retval)); } + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } private: // Actually shows the dialog on the UI thread. diff --git a/chrome/browser/cocoa/html_dialog_window_controller.mm b/chrome/browser/cocoa/html_dialog_window_controller.mm index 32b2096..75c6b6e 100644 --- a/chrome/browser/cocoa/html_dialog_window_controller.mm +++ b/chrome/browser/cocoa/html_dialog_window_controller.mm @@ -42,6 +42,7 @@ public: virtual void GetDialogSize(gfx::Size* size) const; virtual std::string GetDialogArgs() const; virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } // HtmlDialogTabContentsDelegate declarations. virtual void MoveContents(TabContents* source, const gfx::Rect& pos); @@ -288,4 +289,3 @@ void HtmlDialogWindowDelegateBridge::HandleKeyboardEvent( } @end - diff --git a/chrome/browser/cocoa/html_dialog_window_controller_unittest.mm b/chrome/browser/cocoa/html_dialog_window_controller_unittest.mm index e5f1ea7..8b64443 100644 --- a/chrome/browser/cocoa/html_dialog_window_controller_unittest.mm +++ b/chrome/browser/cocoa/html_dialog_window_controller_unittest.mm @@ -32,6 +32,8 @@ public: MOCK_CONST_METHOD1(GetDialogSize, void(gfx::Size*)); MOCK_CONST_METHOD0(GetDialogArgs, std::string()); MOCK_METHOD1(OnDialogClosed, void(const std::string& json_retval)); + MOCK_METHOD2(OnCloseContents, + void(TabContents* source, bool* out_close_dialog)); }; class HtmlDialogWindowControllerTest : public BrowserWithTestWindowTest { diff --git a/chrome/browser/dom_ui/dom_ui_factory.cc b/chrome/browser/dom_ui/dom_ui_factory.cc index bdbfa9a..b9af9ee 100644 --- a/chrome/browser/dom_ui/dom_ui_factory.cc +++ b/chrome/browser/dom_ui/dom_ui_factory.cc @@ -19,6 +19,7 @@ #include "chrome/browser/extensions/extension_dom_ui.h" #include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/extensions/extensions_ui.h" +#include "chrome/browser/printing/print_dialog_cloud.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/url_constants.h" @@ -69,6 +70,12 @@ static DOMUIFactoryFunction GetDOMUIFactoryFunction(const GURL& url) { return &NewDOMUI<PrintUI>; #endif + // All platform builds of Chrome will need to have a cloud printing + // dialog as backup. It's just that on Chrome OS, it's the only + // print dialog. + if (url.host() == chrome::kCloudPrintResourcesHost) + return &NewDOMUI<ExternalHtmlDialogUI>; + // This will get called a lot to check all URLs, so do a quick check of other // schemes (gears was handled above) to filter out most URLs. if (!url.SchemeIs(chrome::kChromeInternalScheme) && diff --git a/chrome/browser/dom_ui/dom_ui_util.cc b/chrome/browser/dom_ui/dom_ui_util.cc new file mode 100644 index 0000000..e247655 --- /dev/null +++ b/chrome/browser/dom_ui/dom_ui_util.cc @@ -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 "chrome/browser/dom_ui/dom_ui_util.h" + +#include "base/logging.h" +#include "base/values.h" + +namespace dom_ui_util { + +std::string GetJsonResponseFromFirstArgumentInList(const Value* content) { + return GetJsonResponseFromArgumentList(content, 0); +} + +std::string GetJsonResponseFromArgumentList(const Value* content, + size_t list_index) { + std::string result; + + if (!content || !content->IsType(Value::TYPE_LIST)) { + NOTREACHED(); + return result; + } + const ListValue* args = static_cast<const ListValue*>(content); + if (args->GetSize() <= list_index) { + NOTREACHED(); + return result; + } + + Value* value = NULL; + if (args->Get(list_index, &value)) + value->GetAsString(&result); + + return result; +} + +} // end of namespace dom_ui_util diff --git a/chrome/browser/dom_ui/dom_ui_util.h b/chrome/browser/dom_ui/dom_ui_util.h new file mode 100644 index 0000000..48c68a1 --- /dev/null +++ b/chrome/browser/dom_ui/dom_ui_util.h @@ -0,0 +1,34 @@ +// 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 CHROME_BROWSER_DOM_UI_DOM_UI_UTIL_H_ +#define CHROME_BROWSER_DOM_UI_DOM_UI_UTIL_H_ + +#include <string> + +class Value; + +namespace dom_ui_util { + +// Convenience routine to get the response string from an argument +// list. Typically used when supporting a DOMUI and getting calls +// from the hosted code. Content must be a ListValue with at least +// one entry in it, and that first entry must be a string, which is +// returned. The parameter is a Value for convenience. Returns an +// empty string on error or if the parameter is not a ListValue. +std::string GetJsonResponseFromFirstArgumentInList(const Value* content); + +// Convenience routine to get one of the response strings from an +// argument list. content must be a ListValue, with at least +// (list_index+1) entries in it. list_index is the 0-based index of +// the entry to pull from that list, and that entry must be a string, +// which is returned. The parameter is a Value for convenience. +// Returns an empty string on error or if the parameter is not a +// ListValue. +std::string GetJsonResponseFromArgumentList(const Value* content, + size_t list_index); + +} // end of namespace + +#endif // CHROME_BROWSER_DOM_UI_DOM_UI_UTIL_H_ diff --git a/chrome/browser/dom_ui/html_dialog_ui.cc b/chrome/browser/dom_ui/html_dialog_ui.cc index 739a4c4..f93e9ca 100644 --- a/chrome/browser/dom_ui/html_dialog_ui.cc +++ b/chrome/browser/dom_ui/html_dialog_ui.cc @@ -7,8 +7,10 @@ #include "base/callback.h" #include "base/singleton.h" #include "base/values.h" +#include "chrome/browser/dom_ui/dom_ui_util.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/common/bindings_policy.h" HtmlDialogUI::HtmlDialogUI(TabContents* tab_contents) : DOMUI(tab_contents) { } @@ -32,28 +34,6 @@ PropertyAccessor<HtmlDialogUIDelegate*>& HtmlDialogUI::GetPropertyAccessor() { //////////////////////////////////////////////////////////////////////////////// // Private: -// Helper function to read the JSON string from the Value parameter. -static std::string GetJsonResponse(const Value* content) { - if (!content || !content->IsType(Value::TYPE_LIST)) { - NOTREACHED(); - return std::string(); - } - const ListValue* args = static_cast<const ListValue*>(content); - if (args->GetSize() != 1) { - NOTREACHED(); - return std::string(); - } - - std::string result; - Value* value = NULL; - if (!args->Get(0, &value) || !value->GetAsString(&result)) { - NOTREACHED(); - return std::string(); - } - - return result; -} - void HtmlDialogUI::RenderViewCreated(RenderViewHost* render_view_host) { // Hook up the javascript function calls, also known as chrome.send("foo") // calls in the HTML, to the actual C++ functions. @@ -70,7 +50,8 @@ void HtmlDialogUI::RenderViewCreated(RenderViewHost* render_view_host) { (*delegate)->GetDOMMessageHandlers(&handlers); } - render_view_host->SetDOMUIProperty("dialogArguments", dialog_args); + if (0 != (bindings_ & BindingsPolicy::DOM_UI)) + render_view_host->SetDOMUIProperty("dialogArguments", dialog_args); for (std::vector<DOMMessageHandler*>::iterator it = handlers.begin(); it != handlers.end(); ++it) { (*it)->Attach(this); @@ -82,5 +63,18 @@ void HtmlDialogUI::OnDialogClosed(const Value* content) { HtmlDialogUIDelegate** delegate = GetPropertyAccessor().GetProperty( tab_contents()->property_bag()); if (delegate) - (*delegate)->OnDialogClosed(GetJsonResponse(content)); + (*delegate)->OnDialogClosed( + dom_ui_util::GetJsonResponseFromFirstArgumentInList(content)); +} + +ExternalHtmlDialogUI::ExternalHtmlDialogUI(TabContents* tab_contents) + : HtmlDialogUI(tab_contents) { + // Non-file based UI needs to not have access to the DOM UI bindings + // for security reasons. The code hosting the dialog should provide + // dialog specific functionality through other bindings and methods + // that are scoped in duration to the dialogs existence. + bindings_ &= ~BindingsPolicy::DOM_UI; +} + +ExternalHtmlDialogUI::~ExternalHtmlDialogUI() { } diff --git a/chrome/browser/dom_ui/html_dialog_ui.h b/chrome/browser/dom_ui/html_dialog_ui.h index ffc5196..be2df7f 100644 --- a/chrome/browser/dom_ui/html_dialog_ui.h +++ b/chrome/browser/dom_ui/html_dialog_ui.h @@ -44,6 +44,12 @@ class HtmlDialogUIDelegate { // A callback to notify the delegate that the dialog closed. virtual void OnDialogClosed(const std::string& json_retval) = 0; + // A callback to notify the delegate that the contents have gone + // away. Only relevant if your dialog hosts code that calls + // windows.close() and you've allowed that. If the output parameter + // is set to true, then the dialog is closed. The default is false. + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) = 0; + protected: ~HtmlDialogUIDelegate() {} }; @@ -89,4 +95,15 @@ class HtmlDialogUI : public DOMUI { DISALLOW_COPY_AND_ASSIGN(HtmlDialogUI); }; +// Displays external URL contents inside a modal HTML dialog. +// +// Intended to be the place to collect the settings and lockdowns +// necessary for running external UI conponents securely (e.g., the +// cloud print dialog). +class ExternalHtmlDialogUI : public HtmlDialogUI { + public: + explicit ExternalHtmlDialogUI(TabContents* tab_contents); + virtual ~ExternalHtmlDialogUI(); +}; + #endif // CHROME_BROWSER_DOM_UI_HTML_DIALOG_UI_H_ diff --git a/chrome/browser/gtk/html_dialog_gtk.h b/chrome/browser/gtk/html_dialog_gtk.h index d292ffb..250f1a0 100644 --- a/chrome/browser/gtk/html_dialog_gtk.h +++ b/chrome/browser/gtk/html_dialog_gtk.h @@ -43,6 +43,7 @@ class HtmlDialogGtk : public HtmlDialogTabContentsDelegate, virtual void GetDialogSize(gfx::Size* size) const; virtual std::string GetDialogArgs() const; virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } // Overridden from TabContentsDelegate: virtual void MoveContents(TabContents* source, const gfx::Rect& pos); diff --git a/chrome/browser/modal_html_dialog_delegate.h b/chrome/browser/modal_html_dialog_delegate.h index 80f83ca..85415ea 100644 --- a/chrome/browser/modal_html_dialog_delegate.h +++ b/chrome/browser/modal_html_dialog_delegate.h @@ -38,6 +38,7 @@ class ModalHtmlDialogDelegate virtual void GetDialogSize(gfx::Size* size) const; virtual std::string GetDialogArgs() const; virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } private: NotificationRegistrar registrar_; diff --git a/chrome/browser/printing/print_dialog_cloud.cc b/chrome/browser/printing/print_dialog_cloud.cc new file mode 100644 index 0000000..02385d2 --- /dev/null +++ b/chrome/browser/printing/print_dialog_cloud.cc @@ -0,0 +1,480 @@ +// 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 "chrome/browser/printing/print_dialog_cloud.h" +#include "chrome/browser/printing/print_dialog_cloud_internal.h" + +#include "app/l10n_util.h" +#include "base/base64.h" +#include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/values.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/dom_ui/dom_ui.h" +#include "chrome/browser/dom_ui/dom_ui_util.h" +#include "chrome/browser/dom_ui/html_dialog_ui.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/url_constants.h" + +#include "grit/generated_resources.h" + +// This module implements the UI support in Chrome for cloud printing. +// This means hosting a dialog containing HTML/JavaScript and using +// the published cloud print user interface integration APIs to get +// page setup settings from the dialog contents and provide the +// generated print PDF to the dialog contents for uploading to the +// cloud print service. + +// Currently, the flow between these classes is as follows: + +// PrintDialogCloud::CreatePrintDialogForPdf is called from +// resource_message_filter_gtk.cc once the renderer has informed the +// renderer host that PDF generation into the renderer host provided +// temp file has been completed. That call is on the IO thread. +// That, in turn, hops over to the UI thread to create an instance of +// PrintDialogCloud. + +// The constructor for PrintDialogCloud creates a +// CloudPrintHtmlDialogDelegate and asks the current active browser to +// show an HTML dialog using that class as the delegate. That class +// hands in the kCloudPrintResourcesURL as the URL to visit. That is +// recognized by the GetDOMUIFactoryFunction as a signal to create an +// ExternalHtmlDialogUI. + +// CloudPrintHtmlDialogDelegate also temporarily owns a +// CloudPrintFlowHandler, a class which is responsible for the actual +// interactions with the dialog contents, including handing in the PDF +// print data and getting any page setup parameters that the dialog +// contents provides. As part of bringing up the dialog, +// HtmlDialogUI::RenderViewCreated is called (an override of +// DOMUI::RenderViewCreated). That routine, in turn, calls the +// delegate's GetDOMMessageHandlers routine, at which point the +// ownership of the CloudPrintFlowHandler is handed over. A pointer +// to the flow handler is kept to facilitate communication back and +// forth between the two classes. + +// The DOMUI continues dialog bring-up, calling +// CloudPrintFlowHandler::RegisterMessages. This is where the +// additional object model capabilities are registered for the dialog +// contents to use. It is also at this time that capabilities for the +// dialog contents are adjusted to allow the dialog contents to close +// the window. In addition, the pending URL is redirected to the +// actual cloud print service URL. The flow controller also registers +// for notification of when the dialog contents finish loading, which +// is currently used to send the PDF data to the dialog contents. + +// In order to send the PDF data to the dialog contents, the flow +// handler uses a CloudPrintDataSender. It creates one, letting it +// know the name of the temporary file containing the PDF data, and +// posts the task of reading the file +// (CloudPrintDataSender::ReadPrintDataFile) to the file thread. That +// routine reads in the file, and then hops over to the IO thread to +// send that data to the dialog contents. + +// When the dialog contents are finished (by either being cancelled or +// hitting the print button), the delegate is notified, and responds +// that the dialog should be closed, at which point things are torn +// down and released. + +// TODO(scottbyer): +// http://code.google.com/p/chromium/issues/detail?id=44093 The +// high-level flow (where the PDF data is generated before even +// bringing up the dialog) isn't what we want. + + +namespace internal_cloud_print_helpers { + +// TODO(scottbyer): Replace with the real public URL when we have one. +// That, and get it into the profile instead of as a hardwired +// constant. +const char* const kCloudPrintDialogUrl = + "http://placeholderurl.ned/printing/client/dialog.html"; + +bool GetRealOrInt(const DictionaryValue& dictionary, + const std::wstring& path, + double* out_value) { + if (!dictionary.GetReal(path, out_value)) { + int int_value = 0; + if (!dictionary.GetInteger(path, &int_value)) + return false; + *out_value = int_value; + } + return true; +} + +// From the JSON parsed value, get the entries for the page setup +// parameters. +bool GetPageSetupParameters(const std::string& json, + ViewMsg_Print_Params& parameters) { + scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); + DLOG_IF(ERROR, (!parsed_value.get() || + !parsed_value->IsType(Value::TYPE_DICTIONARY))) + << "PageSetup call didn't have expected contents"; + if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) + return false; + + bool result = true; + DictionaryValue* params = static_cast<DictionaryValue*>(parsed_value.get()); + result &= GetRealOrInt(*params, L"dpi", ¶meters.dpi); + result &= GetRealOrInt(*params, L"min_shrink", ¶meters.min_shrink); + result &= GetRealOrInt(*params, L"max_shrink", ¶meters.max_shrink); + result &= params->GetBoolean(L"selection_only", ¶meters.selection_only); + return result; +} + +void CloudPrintDataSenderHelper::CallJavascriptFunction( + const std::wstring& function_name) { + dom_ui_->CallJavascriptFunction(function_name); +} + +void CloudPrintDataSenderHelper::CallJavascriptFunction( + const std::wstring& function_name, const Value& arg) { + dom_ui_->CallJavascriptFunction(function_name, arg); +} + +void CloudPrintDataSenderHelper::CallJavascriptFunction( + const std::wstring& function_name, const Value& arg1, const Value& arg2) { + dom_ui_->CallJavascriptFunction(function_name, arg1, arg2); +} + +// Clears out the pointer we're using to communicate. Either routine is +// potentially expensive enough that stopping whatever is in progress +// is worth it. +void CloudPrintDataSender::CancelPrintDataFile() { + AutoLock lock(lock_); + // We don't own helper, it was passed in to us, so no need to + // delete, just let it go. + helper_ = NULL; +} + +// Grab the raw PDF file contents and massage them into shape for +// sending to the dialog contents (and up to the cloud print server) +// by encoding it and prefixing it with the appropriate mime type. +// Once that is done, kick off the next part of the task on the IO +// thread. +void CloudPrintDataSender::ReadPrintDataFile(const FilePath& path_to_pdf) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + int64 file_size = 0; + if (file_util::GetFileSize(path_to_pdf, &file_size) && file_size != 0) { + std::string file_data; + if (file_size < kuint32max) { + file_data.reserve(static_cast<unsigned int>(file_size)); + } else { + DLOG(WARNING) << " print data file too large to reserve space"; + } + if (helper_ && file_util::ReadFileToString(path_to_pdf, &file_data)) { + std::string base64_data; + base::Base64Encode(file_data, &base64_data); + std::string header("data:application/pdf;base64,"); + base64_data.insert(0, header); + scoped_ptr<StringValue> new_data(new StringValue(base64_data)); + print_data_.swap(new_data); + ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, + NewRunnableMethod( + this, + &CloudPrintDataSender::SendPrintDataFile)); + } + } +} + +// We have the data in hand that needs to be pushed into the dialog +// contents; do so from the IO thread. + +// TODO(scottbyer): If the print data ends up being larger than the +// upload limit (currently 10MB), what we need to do is upload that +// large data to google docs and set the URL in the printing +// JavaScript to that location, and make sure it gets deleted when not +// needed. - 4/1/2010 +void CloudPrintDataSender::SendPrintDataFile() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + AutoLock lock(lock_); + if (helper_ && print_data_.get()) { + // TODO(scottbyer) - fill this in with the title or URL of the + // original page. + StringValue title("Chrome Print Test"); + + // Send the print data to the dialog contents. The JavaScript + // function is a preliminary API for prototyping purposes and is + // subject to change. + const_cast<CloudPrintDataSenderHelper*>(helper_)->CallJavascriptFunction( + L"printApp._printDataUrl", *print_data_, title); + } +} + + +void CloudPrintFlowHandler::SetDialogDelegate( + CloudPrintHtmlDialogDelegate* delegate) { + // Even if setting a new dom_ui, it means any previous task needs + // to be cancelled, it's now invalid. + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + CancelAnyRunningTask(); + dialog_delegate_ = delegate; +} + +// Cancels any print data sender we have in flight and removes our +// reference to it, so when the task that is calling it finishes and +// removes it's reference, it goes away. +void CloudPrintFlowHandler::CancelAnyRunningTask() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + if (print_data_sender_.get()) { + print_data_sender_->CancelPrintDataFile(); + print_data_sender_ = NULL; + } +} + + +void CloudPrintFlowHandler::RegisterMessages() { + if (!dom_ui_) + return; + + // TODO(scottbyer) - This is where we will register messages for the + // UI JS to use. Needed: Call to update page setup parameters. + dom_ui_->RegisterMessageCallback( + "ShowDebugger", + NewCallback(this, &CloudPrintFlowHandler::HandleShowDebugger)); + dom_ui_->RegisterMessageCallback( + "SendPrintData", + NewCallback(this, &CloudPrintFlowHandler::HandleSendPrintData)); + dom_ui_->RegisterMessageCallback( + "SetPageParameters", + NewCallback(this, &CloudPrintFlowHandler::HandleSetPageParameters)); + + if (dom_ui_->tab_contents()) { + // Also, take the opportunity to set some (minimal) additional + // script permissions required for the web UI. + + // TODO(scottbyer): learn how to make sure we're talking to the + // right web site first. + RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host(); + if (rvh && rvh->delegate()) { + WebPreferences webkit_prefs = rvh->delegate()->GetWebkitPrefs(); + webkit_prefs.allow_scripts_to_close_windows = true; + rvh->UpdateWebPreferences(webkit_prefs); + } + + // Register for appropriate notifications, and re-direct the URL + // to the real server URL, now that we've gotten an HTML dialog + // going. + NavigationController* controller = &dom_ui_->tab_contents()->controller(); + NavigationEntry* pending_entry = controller->pending_entry(); + if (pending_entry) + pending_entry->set_url(GURL(kCloudPrintDialogUrl)); + registrar_.Add(this, NotificationType::LOAD_STOP, + Source<NavigationController>(controller)); + } +} + +void CloudPrintFlowHandler::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::LOAD_STOP) { + // Choose one or the other. If you need to debug, bring up the + // debugger. You can then use the various chrome.send() + // registrations above to kick of the various function calls, + // including chrome.send("SendPrintData") in the javaScript + // console and watch things happen with: + // HandleShowDebugger(NULL); + HandleSendPrintData(NULL); + } +} + +void CloudPrintFlowHandler::HandleShowDebugger(const Value* value) { + ShowDebugger(); +} + +void CloudPrintFlowHandler::ShowDebugger() { + if (dom_ui_) { + RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host(); + if (rvh) + DevToolsManager::GetInstance()->OpenDevToolsWindow(rvh); + } +} + +scoped_refptr<CloudPrintDataSender> +CloudPrintFlowHandler::CreateCloudPrintDataSender() { + DCHECK(dom_ui_); + print_data_helper_.reset(new CloudPrintDataSenderHelper(dom_ui_)); + return new CloudPrintDataSender(print_data_helper_.get()); +} + +void CloudPrintFlowHandler::HandleSendPrintData(const Value* value) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + // This will cancel any ReadPrintDataFile() or SendPrintDataFile() + // requests in flight (this is anticipation of when setting page + // setup parameters becomes asynchronous and may be set while some + // data is in flight). Then we can clear out the print data. + CancelAnyRunningTask(); + if (dom_ui_) { + print_data_sender_ = CreateCloudPrintDataSender(); + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod( + print_data_sender_.get(), + &CloudPrintDataSender::ReadPrintDataFile, + path_to_pdf_)); + } +} + +void CloudPrintFlowHandler::HandleSetPageParameters(const Value* value) { + std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(value)); + if (json.empty()) + return; + + // These are backstop default values - 72 dpi to match the screen, + // 8.5x11 inch paper with margins subtracted (1/4 inch top, left, + // right and 0.56 bottom), and the min page shrink and max page + // shrink values appear all over the place with no explanation. + + // TODO(scottbyer): Get a Linux/ChromeOS edge for PrintSettings + // working so that we can get the default values from there. Fix up + // PrintWebViewHelper to do the same. + const int kDPI = 72; + const int kWidth = static_cast<int>((8.5-0.25-0.25)*kDPI); + const int kHeight = static_cast<int>((11-0.25-0.56)*kDPI); + const double kMinPageShrink = 1.25; + const double kMaxPageShrink = 2.0; + + ViewMsg_Print_Params default_settings; + default_settings.printable_size = gfx::Size(kWidth, kHeight); + default_settings.dpi = kDPI; + default_settings.min_shrink = kMinPageShrink; + default_settings.max_shrink = kMaxPageShrink; + default_settings.desired_dpi = kDPI; + default_settings.document_cookie = 0; + default_settings.selection_only = false; + + if (!GetPageSetupParameters(json, default_settings)) { + NOTREACHED(); + return; + } + + // TODO(scottbyer) - Here is where we would kick the originating + // renderer thread with these new parameters in order to get it to + // re-generate the PDF and hand it back to us. window.print() is + // currently synchronous, so there's a lot of work to do to get to + // that point. +} + +CloudPrintHtmlDialogDelegate::CloudPrintHtmlDialogDelegate( + const FilePath& path_to_pdf, + int width, int height, + const std::string& json_arguments) + : flow_handler_(new CloudPrintFlowHandler(path_to_pdf)), + owns_flow_handler_(true) { + Init(width, height, json_arguments); +} + +CloudPrintHtmlDialogDelegate::CloudPrintHtmlDialogDelegate( + CloudPrintFlowHandler* flow_handler, + int width, int height, + const std::string& json_arguments) + : flow_handler_(flow_handler), + owns_flow_handler_(false) { + Init(width, height, json_arguments); +} + +void CloudPrintHtmlDialogDelegate::Init( + int width, int height, const std::string& json_arguments) { + // This information is needed to show the dialog HTML content. + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + std::string cloud_print_url(chrome::kCloudPrintResourcesURL); + params_.url = GURL(cloud_print_url); + params_.height = height; + params_.width = width; + params_.json_input = json_arguments; + + flow_handler_->SetDialogDelegate(this); +} + +CloudPrintHtmlDialogDelegate::~CloudPrintHtmlDialogDelegate() { + // If the flow_handler_ is about to outlive us because we don't own + // it anymore, we need to have it remove it's reference to us. + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + flow_handler_->SetDialogDelegate(NULL); + if (owns_flow_handler_) { + delete flow_handler_; + } +} + +bool CloudPrintHtmlDialogDelegate::IsDialogModal() const { + return true; +} + +std::wstring CloudPrintHtmlDialogDelegate::GetDialogTitle() const { + return l10n_util::GetString(IDS_CLOUD_PRINT_TITLE); +} + +GURL CloudPrintHtmlDialogDelegate::GetDialogContentURL() const { + return params_.url; +} + +void CloudPrintHtmlDialogDelegate::GetDOMMessageHandlers( + std::vector<DOMMessageHandler*>* handlers) const { + handlers->push_back(flow_handler_); + // We don't own flow_handler_ anymore, but it sticks around until at + // least right after OnDialogClosed() is called (and this object is + // destroyed). + owns_flow_handler_ = false; +} + +void CloudPrintHtmlDialogDelegate::GetDialogSize(gfx::Size* size) const { + size->set_width(params_.width); + size->set_height(params_.height); +} + +std::string CloudPrintHtmlDialogDelegate::GetDialogArgs() const { + return params_.json_input; +} + +void CloudPrintHtmlDialogDelegate::OnDialogClosed( + const std::string& json_retval) { + delete this; +} + +} // end of namespace internal_cloud_print_helpers + +// static, called on the IO thread. This is the main entry point into +// creating the dialog. + +// TODO(scottbyer): The signature here will need to change as the +// workflow through the printing code changes to allow for dynamically +// changing page setup parameters while the dialog is active. +void PrintDialogCloud::CreatePrintDialogForPdf(const FilePath& path_to_pdf) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableFunction(&PrintDialogCloud::CreateDialogImpl, path_to_pdf)); +} + +// static, called from the UI thread. +void PrintDialogCloud::CreateDialogImpl(const FilePath& path_to_pdf) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + new PrintDialogCloud(path_to_pdf); +} + +// Initialize the print dialog. Called on the UI thread. +PrintDialogCloud::PrintDialogCloud(const FilePath& path_to_pdf) + : browser_(BrowserList::GetLastActive()) { + + // TODO(scottbyer): Verify GAIA login valid, execute GAIA login if not (should + // be distilled out of bookmark sync.) + + // TODO(scottbyer): Get the dialog width, height from the dialog + // contents, and take the screen size into account. + HtmlDialogUIDelegate* dialog_delegate = + new internal_cloud_print_helpers::CloudPrintHtmlDialogDelegate( + path_to_pdf, 500, 400, std::string()); + browser_->BrowserShowHtmlDialog(dialog_delegate, NULL); +} + +PrintDialogCloud::~PrintDialogCloud() { +} diff --git a/chrome/browser/printing/print_dialog_cloud.h b/chrome/browser/printing/print_dialog_cloud.h new file mode 100644 index 0000000..7e8515b --- /dev/null +++ b/chrome/browser/printing/print_dialog_cloud.h @@ -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. + +#ifndef CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_ +#define CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_ + +#include "base/basictypes.h" + +#include "testing/gtest/include/gtest/gtest_prod.h" + +class Browser; +class FilePath; +namespace IPC { +class Message; +} + +class PrintDialogCloud { + public: + // Called on the IO thread. + static void CreatePrintDialogForPdf(const FilePath& path_to_pdf); + + private: + FRIEND_TEST(PrintDialogCloudTest, HandlersRegistered); + + explicit PrintDialogCloud(const FilePath& path_to_pdf); + ~PrintDialogCloud(); + + // Called as a task from the UI thread, creates an object instance + // to run the HTML/JS based print dialog for printing through the cloud. + static void CreateDialogImpl(const FilePath& path_to_pdf); + + Browser* browser_; + + DISALLOW_COPY_AND_ASSIGN(PrintDialogCloud); +}; + +#endif // CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_H_ diff --git a/chrome/browser/printing/print_dialog_cloud_internal.h b/chrome/browser/printing/print_dialog_cloud_internal.h new file mode 100644 index 0000000..1bde98e --- /dev/null +++ b/chrome/browser/printing/print_dialog_cloud_internal.h @@ -0,0 +1,172 @@ +// 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 CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_ +#define CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_ + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/dom_ui/dom_ui.h" +#include "chrome/browser/dom_ui/html_dialog_ui.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class StringValue; + +namespace internal_cloud_print_helpers { + +// TODO(scottbyer): this should really be fetched from a profile +// preference. 4/8/10 +extern const char* const kCloudPrintDialogUrl; + +// Small class to virtualize a few functions to aid with unit testing. +class CloudPrintDataSenderHelper { + public: + explicit CloudPrintDataSenderHelper(DOMUI* dom_ui) : dom_ui_(dom_ui) {} + virtual ~CloudPrintDataSenderHelper() {} + + // Virtualize the overrides of these three functions from DOMUI to + // facilitate unit testing. + virtual void CallJavascriptFunction(const std::wstring& function_name); + virtual void CallJavascriptFunction(const std::wstring& function_name, + const Value& arg); + virtual void CallJavascriptFunction(const std::wstring& function_name, + const Value& arg1, + const Value& arg2); + + private: + DOMUI* dom_ui_; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintDataSenderHelper); +}; + +// Small helper class to get the print data loaded in from the PDF +// file (on the FILE thread) and send it to the print dialog contents +// (on the IO thread), allowing for cancellation. +class CloudPrintDataSender + : public base::RefCountedThreadSafe<CloudPrintDataSender> { + public: + // The owner of this object is also expected to own and control the + // lifetime of the helper. + explicit CloudPrintDataSender(CloudPrintDataSenderHelper* helper) + : helper_(helper) {} + + // Calls to read in the PDF file (on the FILE thread) then send that + // information to the dialog renderer (on the IO thread). We know + // that the dom_ui pointer lifetime will outlast us, so we should be + // good. + void ReadPrintDataFile(const FilePath& path_to_pdf); + void SendPrintDataFile(); + + // Cancels any ramining part of the task by clearing out the dom_ui + // helper_ ptr. + void CancelPrintDataFile(); + + private: + friend class base::RefCountedThreadSafe<CloudPrintDataSender>; + ~CloudPrintDataSender() {} + + Lock lock_; + CloudPrintDataSenderHelper* volatile helper_; + scoped_ptr<StringValue> print_data_; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintDataSender); +}; + +class CloudPrintHtmlDialogDelegate; + +// The CloudPrintFlowHandler connects the state machine (the UI delegate) +// to the dialog backing HTML and JS by providing DOMMessageHandler +// functions for the JS to use. This include refreshing the page +// setup parameters (which will cause a re-generation of the PDF in +// the renderer process - do we want a progress throbber shown? +// Probably..), and packing up the PDF and job parameters and sending +// them to the cloud. +class CloudPrintFlowHandler : public DOMMessageHandler, + public NotificationObserver { + public: + explicit CloudPrintFlowHandler(const FilePath& path_to_pdf) + : path_to_pdf_(path_to_pdf) {} + virtual ~CloudPrintFlowHandler() { + // This will also cancel any task in flight. + CancelAnyRunningTask(); + } + + // DOMMessageHandler implementation. + virtual void RegisterMessages(); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Callbacks from the page. + void HandleShowDebugger(const Value* value); + void HandleSendPrintData(const Value* value); + void HandleSetPageParameters(const Value* value); + + // Call to get the debugger loaded on our hosted dialog page + // specifically. Since we're not in an official browser tab, only + // way to get the debugger going. + void ShowDebugger(); + + void SetDialogDelegate(CloudPrintHtmlDialogDelegate *delegate); + void CancelAnyRunningTask(); + + private: + // For unit testing. + virtual scoped_refptr<CloudPrintDataSender> CreateCloudPrintDataSender(); + + CloudPrintHtmlDialogDelegate* dialog_delegate_; + NotificationRegistrar registrar_; + FilePath path_to_pdf_; + scoped_refptr<CloudPrintDataSender> print_data_sender_; + scoped_ptr<CloudPrintDataSenderHelper> print_data_helper_; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintFlowHandler); +}; + +// State machine used to run the printing dialog. This class is used +// to open and run the html dialog and deletes itself when the dialog +// is closed. +class CloudPrintHtmlDialogDelegate : public HtmlDialogUIDelegate { + public: + CloudPrintHtmlDialogDelegate(const FilePath& path_to_pdf, + int width, int height, + const std::string& json_arguments); + ~CloudPrintHtmlDialogDelegate(); + + // HTMLDialogUIDelegate implementation: + virtual bool IsDialogModal() const; + virtual std::wstring GetDialogTitle() const; + virtual GURL GetDialogContentURL() const; + virtual void GetDOMMessageHandlers( + std::vector<DOMMessageHandler*>* handlers) const; + virtual void GetDialogSize(gfx::Size* size) const; + virtual std::string GetDialogArgs() const; + virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } + + private: + CloudPrintHtmlDialogDelegate(CloudPrintFlowHandler* flow_handler, + int width, int height, + const std::string& json_arguments); + void Init(int width, int height, const std::string& json_arguments); + + CloudPrintFlowHandler* flow_handler_; + mutable bool owns_flow_handler_; + + // The parameters needed to display a modal HTML dialog. + HtmlDialogUI::HtmlDialogParams params_; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintHtmlDialogDelegate); +}; + +} // namespace internal_cloud_print_helpers + +#endif // CHROME_BROWSER_PRINTING_PRINT_DIALOG_CLOUD_INTERNAL_H_ diff --git a/chrome/browser/printing/print_dialog_cloud_uitest.cc b/chrome/browser/printing/print_dialog_cloud_uitest.cc new file mode 100644 index 0000000..753f5ae --- /dev/null +++ b/chrome/browser/printing/print_dialog_cloud_uitest.cc @@ -0,0 +1,176 @@ +// 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 "chrome/browser/printing/print_dialog_cloud.h" +#include "chrome/browser/printing/print_dialog_cloud_internal.h" + +#include <functional> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/singleton.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/dom_ui/chrome_url_data_manager.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/ui_test_utils.h" +#include "net/url_request/url_request_filter.h" +#include "net/url_request/url_request_test_job.h" + +namespace { + +class TestData { + public: + TestData() {} + + char* GetTestData() { + if (test_data_.empty()) { + FilePath test_data_directory; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory); + FilePath test_file = + test_data_directory.AppendASCII("printing/cloud_print_uitest.html"); + file_util::ReadFileToString(test_file, &test_data_); + } + return &test_data_[0]; + } + private: + std::string test_data_; +}; + +// A simple test URLRequestJob. We don't care what it does, only that +// whether it starts and finishes. +class SimpleTestJob : public URLRequestTestJob { + public: + explicit SimpleTestJob(URLRequest* request) + : URLRequestTestJob(request, test_headers(), + Singleton<TestData>()->GetTestData(), true) {} + private: + ~SimpleTestJob() {} +}; + +class TestResult { + public: + TestResult() : result_(false) {} + void SetResult(bool value) { + result_ = value; + } + bool GetResult() { + return result_; + } + private: + bool result_; +}; + +} // namespace + +class PrintDialogCloudTest : public InProcessBrowserTest, + public NotificationObserver { + public: + PrintDialogCloudTest() : handler_added_(false) { + PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_); + GURL cloud_print_url(internal_cloud_print_helpers::kCloudPrintDialogUrl); + host_name_ = cloud_print_url.host(); + } + + // Must be static for handing into AddHostnameHandler. + static URLRequest::ProtocolFactory Factory; + + virtual void SetUp() { + Singleton<TestResult>()->SetResult(false); + InProcessBrowserTest::SetUp(); + } + + virtual void TearDown() { + if (handler_added_) { + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->RemoveHostnameHandler("http", host_name_); + handler_added_ = false; + } + InProcessBrowserTest::TearDown(); + } + + // Normally this is something I would expect could go into SetUp(), + // but there seems to be some timing or ordering related issue with + // the test harness that made that flaky. Calling this from the + // individual test functions seems to fix that. + void AddTestHandlers() { + if (!handler_added_) { + URLRequestFilter* filter = URLRequestFilter::GetInstance(); + filter->AddHostnameHandler("http", host_name_, + &PrintDialogCloudTest::Factory); + handler_added_ = true; + + registrar_.Add(this, NotificationType::LOAD_STOP, + NotificationService::AllSources()); + } + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::LOAD_STOP) { + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask()); + registrar_.Remove(this, NotificationType::LOAD_STOP, + NotificationService::AllSources()); + } + } + + bool handler_added_; + std::string host_name_; + std::string test_data_; + FilePath test_data_directory_; + NotificationRegistrar registrar_; +}; + +URLRequestJob* PrintDialogCloudTest::Factory(URLRequest* request, + const std::string& scheme) { + if (request && (request->url() == + GURL(internal_cloud_print_helpers::kCloudPrintDialogUrl))) + Singleton<TestResult>()->SetResult(true); + return new SimpleTestJob(request); +} + +IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, HandlersRegistered) { + BrowserList::SetLastActive(browser()); + ASSERT_TRUE(BrowserList::GetLastActive()); + + AddTestHandlers(); + + FilePath pdf_file = + test_data_directory_.AppendASCII("printing/cloud_print_uitest.pdf"); + new PrintDialogCloud(pdf_file); + + ui_test_utils::RunMessageLoop(); + + ASSERT_TRUE(Singleton<TestResult>()->GetResult()); +} + +#if defined(OS_CHROMEOS) +// Disabled until the extern URL is live so that the Print menu item +// can be enabled for Chromium OS. +IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, DISABLED_DialogGrabbed) { + BrowserList::SetLastActive(browser()); + ASSERT_TRUE(BrowserList::GetLastActive()); + + AddTestHandlers(); + + // This goes back one step further for the Chrome OS case, to making + // sure 'window.print()' gets to the right place. + ASSERT_TRUE(browser()->GetSelectedTabContents()); + ASSERT_TRUE(browser()->GetSelectedTabContents()->render_view_host()); + + std::wstring window_print(L"window.print()"); + browser()->GetSelectedTabContents()->render_view_host()-> + ExecuteJavascriptInWebFrame(std::wstring(), window_print); + + ui_test_utils::RunMessageLoop(); + + ASSERT_TRUE(Singleton<TestResult>()->GetResult()); +} +#endif diff --git a/chrome/browser/printing/print_dialog_cloud_unittest.cc b/chrome/browser/printing/print_dialog_cloud_unittest.cc new file mode 100644 index 0000000..85a3ce3 --- /dev/null +++ b/chrome/browser/printing/print_dialog_cloud_unittest.cc @@ -0,0 +1,189 @@ +// 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 "chrome/browser/printing/print_dialog_cloud.h" +#include "chrome/browser/printing/print_dialog_cloud_internal.h" + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/values.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using testing::AtLeast; +using testing::Return; +using testing::_; +using testing::A; + +static const char* const kPDFTestFile = "printing/cloud_print_unittest.pdf"; +static const char* const kEmptyPDFTestFile = + "printing/cloud_print_emptytest.pdf"; + +FilePath GetTestDataFileName() { + FilePath test_data_directory; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory); + FilePath test_file = test_data_directory.AppendASCII(kPDFTestFile); + return test_file; +} + +FilePath GetEmptyDataFileName() { + FilePath test_data_directory; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory); + FilePath test_file = test_data_directory.AppendASCII(kEmptyPDFTestFile); + return test_file; +} + +char* GetTestData() { + static std::string sTestFileData; + if (sTestFileData.empty()) { + FilePath test_file = GetTestDataFileName(); + file_util::ReadFileToString(test_file, &sTestFileData); + } + return &sTestFileData[0]; +} + +namespace internal_cloud_print_helpers { + +class MockCloudPrintFlowHandler : public CloudPrintFlowHandler { + public: + MOCK_METHOD0(RegisterMessages, + void()); + MOCK_METHOD3(Observe, + void(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details)); + MOCK_METHOD0(CreateCloudPrintDataSender, + scoped_refptr<CloudPrintDataSender>()); +}; + +class MockCloudPrintHtmlDialogDelegate : public CloudPrintHtmlDialogDelegate { + public: + MOCK_CONST_METHOD0(IsDialogModal, + bool()); + MOCK_CONST_METHOD0(GetDialogTitle, + std::wstring()); + MOCK_CONST_METHOD0(GetDialogContentURL, + GURL()); + MOCK_CONST_METHOD1(GetDOMMessageHandlers, + void(std::vector<DOMMessageHandler*>* handlers)); + MOCK_CONST_METHOD1(GetDialogSize, + void(gfx::Size* size)); + MOCK_CONST_METHOD0(GetDialogArgs, + std::string()); + MOCK_METHOD1(OnDialogClosed, + void(const std::string& json_retval)); + MOCK_METHOD2(OnCloseContents, + void(TabContents* source, bool *out_close_dialog)); +}; + +} // namespace internal_cloud_print_helpers + +using internal_cloud_print_helpers::CloudPrintDataSenderHelper; +using internal_cloud_print_helpers::CloudPrintDataSender; + +class MockExternalHtmlDialogUI : public ExternalHtmlDialogUI { + public: + MOCK_METHOD1(RenderViewCreated, + void(RenderViewHost* render_view_host)); +}; + +class MockCloudPrintDataSenderHelper : public CloudPrintDataSenderHelper { + public: + // TODO(scottbyer): At some point this probably wants to use a + // MockTabContents instead of NULL, and to pre-load it with a bunch + // of expects/results. + MockCloudPrintDataSenderHelper() : CloudPrintDataSenderHelper(NULL) {} + MOCK_METHOD1(CallJavascriptFunction, void(const std::wstring&)); + MOCK_METHOD2(CallJavascriptFunction, void(const std::wstring&, + const Value& arg1)); + MOCK_METHOD3(CallJavascriptFunction, void(const std::wstring&, + const Value& arg1, + const Value& arg2)); +}; + +// Testing for CloudPrintDataSender needs a mock DOMUI. +class CloudPrintDataSenderTest : public testing::Test { + public: + CloudPrintDataSenderTest() + : file_thread_(ChromeThread::FILE, &message_loop_), + io_thread_(ChromeThread::IO, &message_loop_) {} + + protected: + virtual void SetUp() { + mock_helper_.reset(new MockCloudPrintDataSenderHelper); + print_data_sender_ = + new CloudPrintDataSender(mock_helper_.get()); + } + + scoped_refptr<CloudPrintDataSender> print_data_sender_; + scoped_ptr<MockCloudPrintDataSenderHelper> mock_helper_; + + MessageLoop message_loop_; + ChromeThread file_thread_; + ChromeThread io_thread_; +}; + +// TODO(scottbyer): DISABLED until the binary test file can get +// checked in separate from the patch. +TEST_F(CloudPrintDataSenderTest, DISABLED_CanSend) { + EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)). + WillOnce(Return()); + + FilePath test_data_file_name = GetTestDataFileName(); + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod( + print_data_sender_.get(), + &CloudPrintDataSender::ReadPrintDataFile, + test_data_file_name)); + MessageLoop::current()->RunAllPending(); +} + +TEST_F(CloudPrintDataSenderTest, BadFile) { + EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)).Times(0); + +#if defined(OS_WIN) + FilePath bad_data_file_name(L"/some/file/that/isnot/there"); +#else + FilePath bad_data_file_name("/some/file/that/isnot/there"); +#endif + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod( + print_data_sender_.get(), + &CloudPrintDataSender::ReadPrintDataFile, + bad_data_file_name)); + MessageLoop::current()->RunAllPending(); +} + +TEST_F(CloudPrintDataSenderTest, EmptyFile) { + EXPECT_CALL(*mock_helper_, CallJavascriptFunction(_, _, _)).Times(0); + + FilePath empty_data_file_name = GetEmptyDataFileName(); + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod( + print_data_sender_.get(), + &CloudPrintDataSender::ReadPrintDataFile, + empty_data_file_name)); + MessageLoop::current()->RunAllPending(); +} + +// Testing for CloudPrintFlowHandler needs a mock +// CloudPrintHtmlDialogDelegate, mock CloudPrintDataSender, and a mock +// DOMUI. + +// Testing for CloudPrintHtmlDialogDelegate needs a mock +// CloudPrintFlowHandler. + +// Testing for ExternalHtmlDialogUI needs a mock TabContents, mock +// CloudPrintHtmlDialogDelegate (provided through the mock +// tab_contents) + +// Testing for PrintDialogCloud needs a mock Browser. diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc index f27ee9b..6cb3362 100644 --- a/chrome/browser/renderer_host/resource_message_filter.cc +++ b/chrome/browser/renderer_host/resource_message_filter.cc @@ -329,6 +329,8 @@ ResourceMessageFilter::ResourceMessageFilter( DCHECK(dom_storage_dispatcher_host_.get()); render_widget_helper_->Init(id(), resource_dispatcher_host_); + cloud_print_enabled_ = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableCloudPrint); } ResourceMessageFilter::~ResourceMessageFilter() { diff --git a/chrome/browser/renderer_host/resource_message_filter.h b/chrome/browser/renderer_host/resource_message_filter.h index 5eef048..88642cd 100644 --- a/chrome/browser/renderer_host/resource_message_filter.h +++ b/chrome/browser/renderer_host/resource_message_filter.h @@ -416,6 +416,8 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter, // Whether this process is used for off the record tabs. bool off_the_record_; + bool cloud_print_enabled_; + // A callback to create a routing id for the associated renderer process. scoped_ptr<CallbackWithReturnValue<int>::Type> next_route_id_callback_; diff --git a/chrome/browser/renderer_host/resource_message_filter_gtk.cc b/chrome/browser/renderer_host/resource_message_filter_gtk.cc index 3a64462..c4b6530 100644 --- a/chrome/browser/renderer_host/resource_message_filter_gtk.cc +++ b/chrome/browser/renderer_host/resource_message_filter_gtk.cc @@ -16,6 +16,8 @@ #include "chrome/browser/chrome_thread.h" #if defined(TOOLKIT_GTK) #include "chrome/browser/printing/print_dialog_gtk.h" +#else +#include "chrome/browser/printing/print_dialog_cloud.h" #endif #include "chrome/common/chrome_paths.h" #include "chrome/common/render_messages.h" @@ -308,7 +310,10 @@ void ResourceMessageFilter::OnTempFileForPrintingWritten(int fd_in_browser) { #if defined(TOOLKIT_GTK) PrintDialogGtk::CreatePrintDialogForPdf(it->second); #else - NOTIMPLEMENTED(); + if (cloud_print_enabled_) + PrintDialogCloud::CreatePrintDialogForPdf(it->second); + else + NOTIMPLEMENTED(); #endif // Erase the entry in the map. diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc index 1ed639d..b1b9fa1 100644 --- a/chrome/browser/sync/sync_setup_flow.cc +++ b/chrome/browser/sync/sync_setup_flow.cc @@ -17,6 +17,7 @@ #if defined(OS_MACOSX) #include "chrome/browser/cocoa/html_dialog_window_controller_cppsafe.h" #endif +#include "chrome/browser/dom_ui/dom_ui_util.h" #include "chrome/browser/google_service_auth_error.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" @@ -32,28 +33,6 @@ static const wchar_t* kLoginIFrameXPath = L"//iframe[@id='login']"; static const wchar_t* kDoneIframeXPath = L"//iframe[@id='done']"; -// Helper function to read the JSON string from the Value parameter. -static std::string GetJsonResponse(const Value* content) { - if (!content || !content->IsType(Value::TYPE_LIST)) { - NOTREACHED(); - return std::string(); - } - const ListValue* args = static_cast<const ListValue*>(content); - if (args->GetSize() != 1) { - NOTREACHED(); - return std::string(); - } - - std::string result; - Value* value = NULL; - if (!args->Get(0, &value) || !value->GetAsString(&result)) { - NOTREACHED(); - return std::string(); - } - - return result; -} - void FlowHandler::RegisterMessages() { dom_ui_->RegisterMessageCallback("ShowCustomize", NewCallback(this, &FlowHandler::HandleUserClickedCustomize)); @@ -102,7 +81,7 @@ void FlowHandler::ClickCustomizeCancel(const Value* value) { void FlowHandler::HandleSubmitAuth(const Value* value) { - std::string json(GetJsonResponse(value)); + std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(value)); std::string username, password, captcha; if (json.empty()) return; diff --git a/chrome/browser/sync/sync_setup_flow.h b/chrome/browser/sync/sync_setup_flow.h index 4e52ee3..db9de27 100644 --- a/chrome/browser/sync/sync_setup_flow.h +++ b/chrome/browser/sync/sync_setup_flow.h @@ -75,6 +75,9 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { virtual void OnDialogClosed(const std::string& json_retval); // HtmlDialogUIDelegate implementation. + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { } + + // HtmlDialogUIDelegate implementation. virtual std::wstring GetDialogTitle() const { return l10n_util::GetString(IDS_SYNC_MY_BOOKMARKS_LABEL); } diff --git a/chrome/browser/views/html_dialog_view.cc b/chrome/browser/views/html_dialog_view.cc index 68a2c80..0da1cf9 100644 --- a/chrome/browser/views/html_dialog_view.cc +++ b/chrome/browser/views/html_dialog_view.cc @@ -136,6 +136,12 @@ void HtmlDialogView::OnDialogClosed(const std::string& json_retval) { window()->Close(); } +void HtmlDialogView::OnCloseContents(TabContents* source, + bool* out_close_dialog) { + if (delegate_) + delegate_->OnCloseContents(source, out_close_dialog); +} + //////////////////////////////////////////////////////////////////////////////// // TabContentsDelegate implementation: @@ -162,6 +168,13 @@ void HtmlDialogView::HandleKeyboardEvent(const NativeWebKeyboardEvent& event) { #endif } +void HtmlDialogView::CloseContents(TabContents* source) { + bool close_dialog = false; + OnCloseContents(source, &close_dialog); + if (close_dialog) + OnDialogClosed(std::string()); +} + //////////////////////////////////////////////////////////////////////////////// // HtmlDialogView: diff --git a/chrome/browser/views/html_dialog_view.h b/chrome/browser/views/html_dialog_view.h index 6ba6f87..e7d0275 100644 --- a/chrome/browser/views/html_dialog_view.h +++ b/chrome/browser/views/html_dialog_view.h @@ -55,7 +55,7 @@ class HtmlDialogView virtual views::View* GetContentsView(); virtual views::View* GetInitiallyFocusedView(); - // Overridden from HtmlDialogUI::Delegate: + // Overridden from HtmlDialogUIDelegate: virtual bool IsDialogModal() const; virtual std::wstring GetDialogTitle() const; virtual GURL GetDialogContentURL() const; @@ -64,11 +64,13 @@ class HtmlDialogView virtual void GetDialogSize(gfx::Size* size) const; virtual std::string GetDialogArgs() const; virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog); // Overridden from TabContentsDelegate: virtual void MoveContents(TabContents* source, const gfx::Rect& pos); virtual void ToolbarSizeChanged(TabContents* source, bool is_animating); virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event); + virtual void CloseContents(TabContents* source); private: // This view is a delegate to the HTML content since it needs to get notified diff --git a/chrome/browser/views/select_file_dialog.cc b/chrome/browser/views/select_file_dialog.cc index c8cd839..0d523a8 100644 --- a/chrome/browser/views/select_file_dialog.cc +++ b/chrome/browser/views/select_file_dialog.cc @@ -114,6 +114,8 @@ class SelectFileDialogImpl : public SelectFileDialog { virtual void GetDialogSize(gfx::Size* size) const; virtual std::string GetDialogArgs() const; virtual void OnDialogClosed(const std::string& json_retval); + virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) { + } DISALLOW_COPY_AND_ASSIGN(FileBrowseDelegate); }; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index b192909..1382d5c 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -907,6 +907,8 @@ 'browser/dom_ui/dom_ui_theme_source.h', 'browser/dom_ui/dom_ui_thumbnail_source.cc', 'browser/dom_ui/dom_ui_thumbnail_source.h', + 'browser/dom_ui/dom_ui_util.cc', + 'browser/dom_ui/dom_ui_util.h', 'browser/dom_ui/downloads_dom_handler.cc', 'browser/dom_ui/downloads_dom_handler.h', 'browser/dom_ui/downloads_ui.cc', @@ -1755,6 +1757,8 @@ 'browser/pref_store.h', 'browser/printing/print_dialog_gtk.cc', 'browser/printing/print_dialog_gtk.h', + 'browser/printing/print_dialog_cloud.cc', + 'browser/printing/print_dialog_cloud.h', 'browser/printing/print_job.cc', 'browser/printing/print_job.h', 'browser/printing/print_job_manager.cc', @@ -2842,6 +2846,8 @@ ['include', '^browser/dock_info.cc'], ['include', '^browser/dock_info.h'], ['include', '^browser/extensions/'], + ['include', 'browser/printing/print_dialog_cloud.cc'], + ['include', 'browser/printing/print_dialog_cloud.h'], ['include', '^browser/views/about_chrome_view.cc'], ['include', '^browser/views/about_chrome_view.h'], ['include', '^browser/views/accelerator_table_gtk.cc'], @@ -2951,6 +2957,8 @@ ['include', '^browser/views/infobars/*'], ['include', '^browser/views/info_bubble.cc'], ['include', '^browser/views/info_bubble.h'], + ['include', '^browser/views/html_dialog_view.cc'], + ['include', '^browser/views/html_dialog_view.h'], ['include', '^browser/views/location_bar/click_handler.cc'], ['include', '^browser/views/location_bar/click_handler.h'], ['include', '^browser/views/location_bar/content_setting_image_view.cc'], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index f18f78b..f6ce069 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -867,6 +867,7 @@ 'browser/password_manager/password_store_win_unittest.cc', 'browser/pref_member_unittest.cc', 'browser/pref_service_unittest.cc', + 'browser/printing/print_dialog_cloud_unittest.cc', 'browser/printing/print_job_unittest.cc', 'browser/privacy_blacklist/blacklist_interceptor_unittest.cc', 'browser/privacy_blacklist/blacklist_unittest.cc', @@ -1357,6 +1358,7 @@ 'browser/geolocation/geolocation_browsertest.cc', 'browser/net/cookie_policy_browsertest.cc', 'browser/net/ftp_browsertest.cc', + 'browser/printing/print_dialog_cloud_uitest.cc', 'browser/sessions/session_restore_browsertest.cc', 'browser/ssl/ssl_browser_tests.cc', 'browser/task_manager_browsertest.cc', diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index c22fc24..ab8c758 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -89,6 +89,7 @@ extern const char kEnableApps[]; extern const char kEnableAuthNegotiatePort[]; extern const char kEnableBenchmarking[]; extern const char kEnableCloudPrintProxy[]; +extern const char kEnableCloudPrint[]; extern const char kEnableExperimentalExtensionApis[]; extern const char kEnableExperimentalWebGL[]; extern const char kEnableExtensionTimelineApi[]; diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc index 879b2f7..6542fd1 100644 --- a/chrome/common/url_constants.cc +++ b/chrome/common/url_constants.cc @@ -93,6 +93,9 @@ const char kSyncSetupDonePath[] = "setupdone"; const char kAppCacheViewInternalsURL[] = "chrome://appcache-internals/"; +const char kCloudPrintResourcesURL[] = "chrome://cloudprintresources/"; +const char kCloudPrintResourcesHost[] = "cloudprintresources"; + const char kNetworkViewInternalsURL[] = "chrome://net-internals/"; const char kNetworkViewCacheURL[] = "chrome://view-http-cache/"; diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h index 230cc67..e3cf3d9 100644 --- a/chrome/common/url_constants.h +++ b/chrome/common/url_constants.h @@ -93,6 +93,10 @@ extern const char kSyncSetupDonePath[]; // AppCache related URL. extern const char kAppCacheViewInternalsURL[]; +// Cloud Print dialog URL components. +extern const char kCloudPrintResourcesURL[]; +extern const char kCloudPrintResourcesHost[]; + // Network related URLs. extern const char kNetworkViewCacheURL[]; extern const char kNetworkViewInternalsURL[]; diff --git a/chrome/test/data/printing/cloud_print_emptytest.pdf b/chrome/test/data/printing/cloud_print_emptytest.pdf new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/chrome/test/data/printing/cloud_print_emptytest.pdf diff --git a/chrome/test/data/printing/cloud_print_uitest.html b/chrome/test/data/printing/cloud_print_uitest.html new file mode 100644 index 0000000..2b79b4a --- /dev/null +++ b/chrome/test/data/printing/cloud_print_uitest.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Cloud Print Dialog unittest</title> +<script language="javascript"> + window.onload = onLoad; + + function onLoad() { + } + + function testPageSetup() { + var result = JSON.stringify({'dpi': 300, + 'min_shrink': 1.25, + 'max_shrink': 2.0, + 'selection_only': false}); + chrome.send('SetPageParameters', [result]); + } + + function testWindowPrint() { + window.print(); + } + + function testWindowClose() { + window.close(); + } + + function testWindowSizeLarger() { + window.resizeBy(0, 50); + } + + function testWindowSizeSmaller() { + window.resizeBy(0, -50); + } + + function showDebugger() { + onLoad(); + chrome.send('ShowDebugger', ['']); + } + + function testSendPrintData() { + chrome.send('SendPrintData', ['']); + } +</script> +</head> +<body style='margin:0; border:0;'> + <hr> + <table border='0' width='100%'> + <tr> + <td align='right'> + <input type='button' value='Open Debug Window' id='debug' + onclick='showDebugger();'/> + <input type='button' value='Test Send Data' id='data' + onclick='testSendPrintData();'/> + <input type='button' name='test' value='Test window.print()' + onclick='testWindowPrint();'/> + </td> + </tr> + <tr> + <td align='right'> + <input type='button' name='larger' value='Make Window Larger' + onclick='makeWindowLarger();'/> + <input type='button' name='smaller' value='Make Window Smaller' + onclick='makeWindowSmaller();'/> + <input type='button' name='test' value='Test Page Setup' + onclick='testPageSetup();'/> + </td> + </tr> + <tr> + <td align='right'> + <input type='button' value='Cancel' id='cancel' + onclick='testWindowClose();'/> + </td> + </tr> + </table> + <div id='message' style='font-weight:bolder;'> </div> + <div id='advanced' class='advanced' style='display:none;'></div> +</body> +</html> |