From 57427620704c707fa5d50eb6f01a5c4654708c4c Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 1 Aug 2019 17:22:17 +0100 Subject: split browser and browser_window operations --- desktop/Makefile | 3 +- desktop/browser.c | 3477 +------------------------------- desktop/browser_history.c | 3 +- desktop/browser_private.h | 9 +- desktop/browser_window.c | 3490 +++++++++++++++++++++++++++++++++ desktop/local_history.c | 1 + desktop/search.c | 5 +- desktop/selection.c | 1 + desktop/textinput.c | 1 + frontends/amiga/font.c | 2 +- frontends/framebuffer/font_freetype.c | 2 +- frontends/gtk/gui.c | 1 + frontends/windows/main.c | 23 + frontends/windows/window.c | 25 - include/netsurf/browser.h | 42 + include/netsurf/browser_window.h | 17 +- 16 files changed, 3580 insertions(+), 3522 deletions(-) create mode 100644 desktop/browser_window.c create mode 100644 include/netsurf/browser.h diff --git a/desktop/Makefile b/desktop/Makefile index ffd932177..c3c876642 100644 --- a/desktop/Makefile +++ b/desktop/Makefile @@ -12,7 +12,8 @@ desktop/version.c: testament $(OBJROOT)/testament.h # S_BROWSER are sources related to full browsers but are common # between RISC OS, GTK, BeOS and AmigaOS builds -S_BROWSER := browser.c browser_history.c download.c frames.c netsurf.c \ +S_BROWSER := browser.c browser_window.c browser_history.c \ + download.c frames.c netsurf.c \ save_complete.c save_text.c selection.c textinput.c gui_factory.c \ save_pdf.c font_haru.c diff --git a/desktop/browser.c b/desktop/browser.c index 663bfd01b..c04488063 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -1,11 +1,5 @@ /* - * Copyright 2003 Phil Mellor - * Copyright 2006 James Bursa - * Copyright 2004 Andrew Timmins - * Copyright 2004 John Tytgat - * Copyright 2006 Richard Wilson - * Copyright 2008 Michael Drake - * Copyright 2009 Paul Blokus + * Copyright 2019 Vincent Sanders * * This file is part of NetSurf, http://www.netsurf-browser.org/ * @@ -25,3399 +19,14 @@ /** * \file * - * Browser window creation and manipulation implementation. + * Browser core functionality */ -#include "utils/config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils/corestrings.h" -#include "utils/log.h" -#include "utils/messages.h" -#include "utils/nsurl.h" -#include "utils/utils.h" -#include "utils/utf8.h" -#include "utils/nsoption.h" -#include "netsurf/misc.h" -#include "netsurf/window.h" -#include "netsurf/content.h" -#include "netsurf/plotters.h" -#include "content/content_debug.h" -#include "content/fetch.h" -#include "content/hlcache.h" -#include "content/urldb.h" -#include "css/utils.h" -#include "html/form_internal.h" -#include "html/html.h" -#include "html/box.h" -#include "javascript/js.h" - -#include "desktop/browser_history.h" -#include "desktop/browser_private.h" -#include "desktop/download.h" -#include "desktop/frames.h" -#include "desktop/global_history.h" -#include "desktop/hotlist.h" -#include "desktop/knockout.h" -#include "desktop/scrollbar.h" -#include "desktop/selection.h" -#include "desktop/theme.h" -#include "desktop/gui_internal.h" -#include "desktop/textinput.h" - - -/** maximum frame depth */ -#define FRAME_DEPTH 8 - - -/** - * Get position of scrollbar widget within browser window. - * - * \param bw The browser window - * \param horizontal Whether to get position of horizontal scrollbar - * \param x Updated to x-coord of top left of scrollbar widget - * \param y Updated to y-coord of top left of scrollbar widget - */ -static inline void browser_window_get_scrollbar_pos(struct browser_window *bw, - bool horizontal, int *x, int *y) -{ - if (horizontal) { - *x = 0; - *y = bw->height - SCROLLBAR_WIDTH; - } else { - *x = bw->width - SCROLLBAR_WIDTH; - *y = 0; - } -} - - -/** - * Get browser window scrollbar widget length - * - * \param bw The browser window - * \param horizontal Whether to get length of horizontal scrollbar - * \return the scrollbar's length - */ -static inline int browser_window_get_scrollbar_len(struct browser_window *bw, - bool horizontal) -{ - if (horizontal) - return bw->width - (bw->scroll_y != NULL ? SCROLLBAR_WIDTH : 0); - else - return bw->height; -} - - -/* exported interface, documented in browser.h */ -nserror -browser_window_get_name(struct browser_window *bw, const char **out_name) -{ - assert(bw != NULL); - - *out_name = bw->name; - - return NSERROR_OK; -} - - -/* exported interface, documented in browser.h */ -nserror -browser_window_set_name(struct browser_window *bw, const char *name) -{ - char *nname = NULL; - - assert(bw != NULL); - - if (name != NULL) { - nname = strdup(name); - if (nname == NULL) { - return NSERROR_NOMEM; - } - } - - if (bw->name != NULL) { - free(bw->name); - } - - bw->name = nname; - - return NSERROR_OK; -} - - -/* exported interface, documented in browser.h */ -bool -browser_window_redraw(struct browser_window *bw, - int x, int y, - const struct rect *clip, - const struct redraw_context *ctx) -{ - struct redraw_context new_ctx = *ctx; - int width = 0; - int height = 0; - bool plot_ok = true; - content_type content_type; - struct content_redraw_data data; - struct rect content_clip; - nserror res; - - if (bw == NULL) { - NSLOG(netsurf, INFO, "NULL browser window"); - return false; - } - - if ((bw->current_content == NULL) && - (bw->children == NULL)) { - /* Browser window has no content, render blank fill */ - ctx->plot->clip(ctx, clip); - return (ctx->plot->rectangle(ctx, plot_style_fill_white, clip) == NSERROR_OK); - } - - /* Browser window has content OR children (frames) */ - if ((bw->window != NULL) && - (ctx->plot->option_knockout)) { - /* Root browser window: start knockout */ - knockout_plot_start(ctx, &new_ctx); - } - - new_ctx.plot->clip(ctx, clip); - - /* Handle redraw of any browser window children */ - if (bw->children) { - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - if (bw->window != NULL) { - /* Root browser window; start with blank fill */ - plot_ok &= (new_ctx.plot->rectangle(ctx, - plot_style_fill_white, - clip) == NSERROR_OK); - } - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - child = &bw->children[cur_child]; - - /* Get frame edge box in global coordinates */ - content_clip.x0 = (x + child->x) * child->scale; - content_clip.y0 = (y + child->y) * child->scale; - content_clip.x1 = content_clip.x0 + - child->width * child->scale; - content_clip.y1 = content_clip.y0 + - child->height * child->scale; - - /* Intersect it with clip rectangle */ - if (content_clip.x0 < clip->x0) - content_clip.x0 = clip->x0; - if (content_clip.y0 < clip->y0) - content_clip.y0 = clip->y0; - if (clip->x1 < content_clip.x1) - content_clip.x1 = clip->x1; - if (clip->y1 < content_clip.y1) - content_clip.y1 = clip->y1; - - /* Skip this frame if it lies outside clip rectangle */ - if (content_clip.x0 >= content_clip.x1 || - content_clip.y0 >= content_clip.y1) - continue; - - /* Redraw frame */ - plot_ok &= browser_window_redraw(child, - x + child->x, y + child->y, - &content_clip, &new_ctx); - } - - /* Nothing else to redraw for browser windows with children; - * cleanup and return - */ - if (bw->window != NULL && ctx->plot->option_knockout) { - /* Root browser window: knockout end */ - knockout_plot_end(ctx); - } - - return plot_ok; - } - - /* Handle browser windows with content to redraw */ - - content_type = content_get_type(bw->current_content); - if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) { - /* Set render area according to scale */ - width = content_get_width(bw->current_content) * bw->scale; - height = content_get_height(bw->current_content) * bw->scale; - - /* Non-HTML may not fill viewport to extents, so plot white - * background fill */ - plot_ok &= (new_ctx.plot->rectangle(&new_ctx, - plot_style_fill_white, - clip) == NSERROR_OK); - } - - /* Set up content redraw data */ - data.x = x - scrollbar_get_offset(bw->scroll_x); - data.y = y - scrollbar_get_offset(bw->scroll_y); - data.width = width; - data.height = height; - - data.background_colour = 0xFFFFFF; - data.scale = bw->scale; - data.repeat_x = false; - data.repeat_y = false; - - content_clip = *clip; - - if (!bw->window) { - int x0 = x * bw->scale; - int y0 = y * bw->scale; - int x1 = (x + bw->width - ((bw->scroll_y != NULL) ? - SCROLLBAR_WIDTH : 0)) * bw->scale; - int y1 = (y + bw->height - ((bw->scroll_x != NULL) ? - SCROLLBAR_WIDTH : 0)) * bw->scale; - - if (content_clip.x0 < x0) content_clip.x0 = x0; - if (content_clip.y0 < y0) content_clip.y0 = y0; - if (x1 < content_clip.x1) content_clip.x1 = x1; - if (y1 < content_clip.y1) content_clip.y1 = y1; - } - - /* Render the content */ - plot_ok &= content_redraw(bw->current_content, &data, - &content_clip, &new_ctx); - - /* Back to full clip rect */ - new_ctx.plot->clip(&new_ctx, clip); - - if (!bw->window) { - /* Render scrollbars */ - int off_x, off_y; - if (bw->scroll_x != NULL) { - browser_window_get_scrollbar_pos(bw, true, - &off_x, &off_y); - res = scrollbar_redraw(bw->scroll_x, - x + off_x, y + off_y, clip, - bw->scale, &new_ctx); - if (res != NSERROR_OK) { - plot_ok = false; - } - } - if (bw->scroll_y != NULL) { - browser_window_get_scrollbar_pos(bw, false, - &off_x, &off_y); - res = scrollbar_redraw(bw->scroll_y, - x + off_x, y + off_y, clip, - bw->scale, &new_ctx); - if (res != NSERROR_OK) { - plot_ok = false; - } - } - } - - if (bw->window != NULL && ctx->plot->option_knockout) { - /* Root browser window: end knockout */ - knockout_plot_end(ctx); - } - - return plot_ok; -} - - -/* exported interface, documented in browser.h */ -bool browser_window_redraw_ready(struct browser_window *bw) -{ - if (bw == NULL) { - NSLOG(netsurf, INFO, "NULL browser window"); - return false; - } else if (bw->current_content != NULL) { - /* Can't render locked contents */ - return !content_is_locked(bw->current_content); - } - - return true; -} - - -/* exported interface, documented in browser_private.h */ -void browser_window_update_extent(struct browser_window *bw) -{ - if (bw->window != NULL) - /* Front end window */ - guit->window->update_extent(bw->window); - else - /* Core-managed browser window */ - browser_window_handle_scrollbars(bw); -} - - -/* exported interface, documented in browser.h */ -void -browser_window_get_position(struct browser_window *bw, - bool root, - int *pos_x, - int *pos_y) -{ - *pos_x = 0; - *pos_y = 0; - - assert(bw != NULL); - - while (bw) { - switch (bw->browser_window_type) { - - case BROWSER_WINDOW_FRAMESET: - *pos_x += bw->x * bw->scale; - *pos_y += bw->y * bw->scale; - break; - - case BROWSER_WINDOW_NORMAL: - /* There is no offset to the root browser window */ - break; - - case BROWSER_WINDOW_FRAME: - /* Iframe and Frame handling is identical; - * fall though */ - case BROWSER_WINDOW_IFRAME: - *pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) * - bw->scale; - *pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) * - bw->scale; - break; - } - - bw = bw->parent; - - if (!root) { - /* return if we just wanted the position in the parent - * browser window. */ - return; - } - } -} - - -/* exported interface, documented in browser.h */ -void browser_window_set_position(struct browser_window *bw, int x, int y) -{ - assert(bw != NULL); - - if (bw->window == NULL) { - /* Core managed browser window */ - bw->x = x; - bw->y = y; - } else { - NSLOG(netsurf, INFO, - "Asked to set position of front end window."); - assert(0); - } -} - -/* exported interface, documented in browser.h */ -void -browser_window_set_drag_type(struct browser_window *bw, - browser_drag_type type, - const struct rect *rect) -{ - struct browser_window *top_bw = browser_window_get_root(bw); - gui_drag_type gtype; - - bw->drag.type = type; - - if (type == DRAGGING_NONE) { - top_bw->drag.window = NULL; - } else { - top_bw->drag.window = bw; - - switch (type) { - case DRAGGING_SELECTION: - /** \todo tell front end */ - return; - case DRAGGING_SCR_X: - case DRAGGING_SCR_Y: - case DRAGGING_CONTENT_SCROLLBAR: - gtype = GDRAGGING_SCROLLBAR; - break; - default: - gtype = GDRAGGING_OTHER; - break; - } - - guit->window->drag_start(top_bw->window, gtype, rect); - } -} - -/* exported interface, documented in browser.h */ -browser_drag_type browser_window_get_drag_type(struct browser_window *bw) -{ - return bw->drag.type; -} - -/* exported interface, documented in browser.h */ -struct browser_window * browser_window_get_root(struct browser_window *bw) -{ - while (bw && bw->parent) { - bw = bw->parent; - } - return bw; -} - -/* exported interface, documented in browser.h */ -browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw) -{ - browser_editor_flags ed_flags = BW_EDITOR_NONE; - assert(bw->window); - assert(bw->parent == NULL); - - if (bw->selection.bw != NULL) { - ed_flags |= BW_EDITOR_CAN_COPY; - - if (!bw->selection.read_only) - ed_flags |= BW_EDITOR_CAN_CUT; - } - - if (bw->can_edit) - ed_flags |= BW_EDITOR_CAN_PASTE; - - return ed_flags; -} - -/* exported interface, documented in browser.h */ -bool browser_window_can_select(struct browser_window *bw) -{ - if (bw == NULL || bw->current_content == NULL) - return false; - - /* TODO: We shouldn't have to know about specific content types - * here. There should be a content_is_selectable() call. */ - if (content_get_type(bw->current_content) != CONTENT_HTML && - content_get_type(bw->current_content) != - CONTENT_TEXTPLAIN) - return false; - - return true; -} - -/* exported interface, documented in browser.h */ -char * browser_window_get_selection(struct browser_window *bw) -{ - assert(bw->window); - assert(bw->parent == NULL); - - if (bw->selection.bw == NULL || - bw->selection.bw->current_content == NULL) - return NULL; - - return content_get_selection(bw->selection.bw->current_content); -} - -/* exported interface, documented in netsurf/browser_window.h */ -bool browser_window_can_search(struct browser_window *bw) -{ - if (bw == NULL || bw->current_content == NULL) - return false; - - /** \todo We shouldn't have to know about specific content - * types here. There should be a content_is_searchable() call. - */ - if ((content_get_type(bw->current_content) != CONTENT_HTML) && - (content_get_type(bw->current_content) != CONTENT_TEXTPLAIN)) { - return false; - } - - return true; -} - - -/* exported interface, documented in netsurf/browser_window.h */ -bool browser_window_is_frameset(struct browser_window *bw) -{ - return (bw->children != NULL); -} - - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_get_scrollbar_type(struct browser_window *bw, - browser_scrolling *h, browser_scrolling *v) -{ - *h = bw->scrolling; - *v = bw->scrolling; - - return NSERROR_OK; -} - -/** - * Set or remove a selection. - * - * \param bw browser window with selection - * \param selection true if bw has a selection, false if removing selection - * \param read_only true iff selection is read only (e.g. can't cut it) - */ -static void browser_window_set_selection(struct browser_window *bw, - bool selection, bool read_only) -{ - struct browser_window *top; - - assert(bw != NULL); - - top = browser_window_get_root(bw); - - assert(top != NULL); - - if (bw != top->selection.bw && top->selection.bw != NULL && - top->selection.bw->current_content != NULL) { - /* clear old selection */ - content_clear_selection(top->selection.bw->current_content); - } - - if (selection) { - top->selection.bw = bw; - } else { - top->selection.bw = NULL; - } - - top->selection.read_only = read_only; -} - - -/** - * Set the scroll position of a browser window. - * - * scrolls the viewport to ensure the specified rectangle of the - * content is shown. - * - * \param bw window to scroll - * \param rect The rectangle to ensure is shown. - * \return NSERROR_OK on success or apropriate error code. - */ -static nserror -browser_window_set_scroll(struct browser_window *bw, - const struct rect *rect) -{ - if (bw->window != NULL) { - return guit->window->set_scroll(bw->window, rect); - } - - if (bw->scroll_x != NULL) { - scrollbar_set(bw->scroll_x, rect->x0, false); - } - if (bw->scroll_y != NULL) { - scrollbar_set(bw->scroll_y, rect->y0, false); - } - - return NSERROR_OK; -} - -/** - * Internal helper for getting the positional features - * - * \param[in] bw browser window to examine. - * \param[in] x x-coordinate of point of interest - * \param[in] y y-coordinate of point of interest - * \param[out] data Feature structure to update. - * \return NSERROR_OK or appropriate error code on faliure. - */ -static nserror -browser_window__get_contextual_content(struct browser_window *bw, - int x, int y, struct browser_window_features *data) -{ - nserror ret = NSERROR_OK; - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child. - */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if ((x < bwc->x) || - (bwc->x + bwc->width < x) || - (y < bwc->y) || - (bwc->y + bwc->height < y)) { - continue; - } - - /* Pass request into this child */ - return browser_window__get_contextual_content(bwc, - (x - bwc->x), (y - bwc->y), data); - } - - /* Coordinate not contained by any frame */ - - } else if (bw->current_content != NULL) { - /* Pass request to content */ - ret = content_get_contextual_content(bw->current_content, - x, y, data); - data->main = bw->current_content; - } - - return ret; -} - -/* exported interface, documented in browser.h */ -nserror browser_window_get_features(struct browser_window *bw, - int x, int y, struct browser_window_features *data) -{ - /* clear the features structure to empty values */ - data->link = NULL; - data->object = NULL; - data->main = NULL; - data->form_features = CTX_FORM_NONE; - - return browser_window__get_contextual_content(bw, x, y, data); -} - -/* exported interface, documented in browser.h */ -bool browser_window_scroll_at_point(struct browser_window *bw, - int x, int y, int scrx, int scry) -{ - bool handled_scroll = false; - assert(bw != NULL); - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if (x < bwc->x || bwc->x + bwc->width < x || - y < bwc->y || bwc->y + bwc->height < y) - continue; - - /* Pass request into this child */ - return browser_window_scroll_at_point(bwc, - (x - bwc->x), (y - bwc->y), - scrx, scry); - } - } - - /* Try to scroll any current content */ - if (bw->current_content != NULL && content_scroll_at_point( - bw->current_content, x, y, scrx, scry) == true) - /* Scroll handled by current content */ - return true; - - /* Try to scroll this window, if scroll not already handled */ - if (handled_scroll == false) { - if (bw->scroll_y && scrollbar_scroll(bw->scroll_y, scry)) - handled_scroll = true; - - if (bw->scroll_x && scrollbar_scroll(bw->scroll_x, scrx)) - handled_scroll = true; - } - - return handled_scroll; -} - -/* exported interface, documented in browser.h */ -bool browser_window_drop_file_at_point(struct browser_window *bw, - int x, int y, char *file) -{ - assert(bw != NULL); - - /* Handle (i)frame scroll offset (core-managed browser windows only) */ - x += scrollbar_get_offset(bw->scroll_x); - y += scrollbar_get_offset(bw->scroll_y); - - if (bw->children) { - /* Browser window has children, so pass request on to - * appropriate child */ - struct browser_window *bwc; - int cur_child; - int children = bw->rows * bw->cols; - - /* Loop through all children of bw */ - for (cur_child = 0; cur_child < children; cur_child++) { - /* Set current child */ - bwc = &bw->children[cur_child]; - - /* Skip this frame if (x, y) coord lies outside */ - if (x < bwc->x || bwc->x + bwc->width < x || - y < bwc->y || bwc->y + bwc->height < y) - continue; - - /* Pass request into this child */ - return browser_window_drop_file_at_point(bwc, - (x - bwc->x), (y - bwc->y), - file); - } - } - - /* Pass file drop on to any content */ - if (bw->current_content != NULL) - return content_drop_file_at_point(bw->current_content, - x, y, file); - - return false; -} - -/* exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_gadget_filename(struct browser_window *bw, - struct form_control *gadget, const char *fn) -{ - html_set_file_gadget_filename(bw->current_content, gadget, fn); -} - -/* exported interface, documented in browser.h */ -nserror browser_window_debug_dump(struct browser_window *bw, - FILE *f, enum content_debug op) -{ - if (bw->current_content != NULL) { - return content_debug_dump(bw->current_content, f, op); - } - return NSERROR_OK; -} - -/* exported interface, documented in browser.h */ -nserror browser_window_debug(struct browser_window *bw, enum content_debug op) -{ - if (bw->current_content != NULL) { - return content_debug(bw->current_content, op); - } - return NSERROR_OK; -} - -/** slow script handler -*/ -static bool slow_script(void *ctx) -{ - static int count = 0; - NSLOG(netsurf, INFO, "Continuing execution %d", count); - count++; - if (count > 1) { - count = 0; - return false; - } - return true; -} - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_create(enum browser_window_create_flags flags, - nsurl *url, nsurl *referrer, - struct browser_window *existing, - struct browser_window **bw) -{ - gui_window_create_flags gw_flags = GW_CREATE_NONE; - struct browser_window *ret; - nserror err; - - /* Check parameters */ - if (flags & BW_CREATE_CLONE) { - if (existing == NULL) { - assert(0 && "Failed: No existing window provided."); - return NSERROR_BAD_PARAMETER; - } - } - if (!(flags & BW_CREATE_HISTORY)) { - if (!(flags & BW_CREATE_CLONE) || existing == NULL) { - assert(0 && "Failed: Must have existing for history."); - return NSERROR_BAD_PARAMETER; - } - } - - - if ((ret = calloc(1, sizeof(struct browser_window))) == NULL) { - return NSERROR_NOMEM; - } - - /* Initialise common parts */ - err = browser_window_initialise_common(flags, ret, existing); - if (err != NSERROR_OK) { - browser_window_destroy(ret); - return err; - } - - /* window characteristics */ - ret->browser_window_type = BROWSER_WINDOW_NORMAL; - ret->scrolling = BW_SCROLLING_YES; - ret->border = true; - ret->no_resize = true; - ret->focus = ret; - - /* initialise last action with creation time */ - nsu_getmonotonic_ms(&ret->last_action); - - /* The existing gui_window is on the top-level existing - * browser_window. */ - existing = browser_window_get_root(existing); - - /* Set up gui_window creation flags */ - if (flags & BW_CREATE_TAB) - gw_flags |= GW_CREATE_TAB; - if (flags & BW_CREATE_CLONE) - gw_flags |= GW_CREATE_CLONE; - - ret->window = guit->window->create(ret, - (existing != NULL) ? existing->window : NULL, - gw_flags); - - if (ret->window == NULL) { - browser_window_destroy(ret); - return NSERROR_BAD_PARAMETER; - } - - if (url != NULL) { - enum browser_window_nav_flags nav_flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE; - if (flags & BW_CREATE_UNVERIFIABLE) - nav_flags |= BW_NAVIGATE_UNVERIFIABLE; - if (flags & BW_CREATE_HISTORY) - nav_flags |= BW_NAVIGATE_HISTORY; - browser_window_navigate(ret, url, referrer, nav_flags, NULL, - NULL, NULL); - } - - if (bw != NULL) { - *bw = ret; - } - - return NSERROR_OK; -} - - -/* exported internal interface, documented in desktop/browser_private.h */ -nserror browser_window_initialise_common(enum browser_window_create_flags flags, - struct browser_window *bw, struct browser_window *existing) -{ - nserror err; - assert(bw); - - /* new javascript context for each window/(i)frame */ - err = js_newcontext(nsoption_int(script_timeout), - slow_script, NULL, &bw->jsctx); - if (err != NSERROR_OK) - return err; - - if (flags & BW_CREATE_CLONE) { - assert(existing != NULL); - - /* clone history */ - err = browser_window_history_clone(existing, bw); - - /* copy the scale */ - bw->scale = existing->scale; - } else { - /* create history */ - err = browser_window_history_create(bw); - - /* default scale */ - bw->scale = (float) nsoption_int(scale) / 100.0; - } - - if (err != NSERROR_OK) - return err; - - /* window characteristics */ - bw->refresh_interval = -1; - - bw->drag.type = DRAGGING_NONE; - - bw->scroll_x = NULL; - bw->scroll_y = NULL; - - bw->focus = NULL; - - /* initialise status text cache */ - bw->status.text = NULL; - bw->status.text_len = 0; - bw->status.match = 0; - bw->status.miss = 0; - - return NSERROR_OK; -} - -/** - * implements the download operation of a window navigate - */ -static nserror -browser_window_download(struct browser_window *bw, - nsurl *url, - nsurl *nsref, - uint32_t fetch_flags, - bool fetch_is_post, - llcache_post_data *post) -{ - llcache_handle *l; - struct browser_window *root; - nserror error; - - root = browser_window_get_root(bw); - assert(root != NULL); - - fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH; - fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA; - - error = llcache_handle_retrieve(url, fetch_flags, nsref, - fetch_is_post ? post : NULL, - NULL, NULL, &l); - if (error == NSERROR_NO_FETCH_HANDLER) { - /* no internal handler for this type, call out to frontend */ - error = guit->misc->launch_url(url); - } else if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, "Failed to fetch download: %d", error); - } else { - error = download_context_create(l, root->window); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Failed creating download context: %d", error); - llcache_handle_abort(l); - llcache_handle_release(l); - } - } - - return error; -} - - -static bool browser_window_check_throbber(struct browser_window *bw) -{ - int children, index; - - if (bw->throbbing) - return true; - - if (bw->children) { - children = bw->rows * bw->cols; - for (index = 0; index < children; index++) { - if (browser_window_check_throbber(&bw->children[index])) - return true; - } - } - if (bw->iframes) { - for (index = 0; index < bw->iframe_count; index++) { - if (browser_window_check_throbber(&bw->iframes[index])) - return true; - } - } - return false; -} - - -/** - * Start the busy indicator. - * - * \param bw browser window - */ - -static void browser_window_start_throbber(struct browser_window *bw) -{ - bw->throbbing = true; - - while (bw->parent) - bw = bw->parent; - - guit->window->start_throbber(bw->window); -} - - -/** - * Stop the busy indicator. - * - * \param bw browser window - */ -static void browser_window_stop_throbber(struct browser_window *bw) -{ - bw->throbbing = false; - - while (bw->parent) - bw = bw->parent; - - if (!browser_window_check_throbber(bw)) { - guit->window->stop_throbber(bw->window); - } -} - - - -/** - * Callback for fetchcache() for browser window favicon fetches. - * - * \param c content handle of favicon - * \param event The event to process - * \param pw a context containing the browser window - * \return NSERROR_OK on success else appropriate error code. - */ -static nserror -browser_window_favicon_callback(hlcache_handle *c, - const hlcache_event *event, - void *pw) -{ - struct browser_window *bw = pw; - - switch (event->type) { - case CONTENT_MSG_DONE: - if (bw->favicon.current != NULL) { - content_close(bw->favicon.current); - hlcache_handle_release(bw->favicon.current); - } - - bw->favicon.current = c; - bw->favicon.loading = NULL; - - /* content_get_bitmap on the hlcache_handle should give - * the favicon bitmap at this point - */ - guit->window->set_icon(bw->window, c); - break; - - case CONTENT_MSG_ERROR: - case CONTENT_MSG_ERRORCODE: - - /* clean up after ourselves */ - if (c == bw->favicon.loading) { - bw->favicon.loading = NULL; - } else if (c == bw->favicon.current) { - bw->favicon.current = NULL; - } - - hlcache_handle_release(c); - - if (bw->favicon.failed == false) { - nsurl *nsref = NULL; - nsurl *nsurl; - nserror error; - - bw->favicon.failed = true; - - error = nsurl_create("resource:favicon.ico", &nsurl); - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Unable to create default location url"); - } else { - hlcache_handle_retrieve(nsurl, - HLCACHE_RETRIEVE_SNIFF_TYPE, - nsref, NULL, - browser_window_favicon_callback, - bw, NULL, CONTENT_IMAGE, - &bw->favicon.loading); - - nsurl_unref(nsurl); - } - - } - break; - - default: - break; - } - return NSERROR_OK; -} - - -/** - * update the favicon associated with the browser window - * - * \param c the page content handle. - * \param bw A top level browser window. - * \param link A link context or NULL to attempt fallback scanning. - */ -static void -browser_window_update_favicon(hlcache_handle *c, - struct browser_window *bw, - struct content_rfc5988_link *link) -{ - nsurl *nsref = NULL; - nsurl *nsurl; - nserror error; - - assert(c != NULL); - assert(bw !=NULL); - - if (bw->window == NULL) - /* Not top-level browser window; not interested */ - return; - - /* already fetching the favicon - use that */ - if (bw->favicon.loading != NULL) - return; - - bw->favicon.failed = false; - - if (link == NULL) { - /* Look for "icon" */ - link = content_find_rfc5988_link(c, corestring_lwc_icon); - } - - if (link == NULL) { - /* Look for "shortcut icon" */ - link = content_find_rfc5988_link(c, - corestring_lwc_shortcut_icon); - } - - if (link == NULL) { - lwc_string *scheme; - bool speculative_default = false; - bool match; - - nsurl = hlcache_handle_get_url(c); - - scheme = nsurl_get_component(nsurl, NSURL_SCHEME); - - /* If the document was fetched over http(s), then speculate - * that there's a favicon living at /favicon.ico */ - if ((lwc_string_caseless_isequal(scheme, corestring_lwc_http, - &match) == lwc_error_ok && match) || - (lwc_string_caseless_isequal(scheme, corestring_lwc_https, - &match) == lwc_error_ok && match)) { - speculative_default = true; - } - - lwc_string_unref(scheme); - - if (speculative_default) { - /* no favicon via link, try for the default location */ - error = nsurl_join(nsurl, "/favicon.ico", &nsurl); - } else { - bw->favicon.failed = true; - error = nsurl_create("resource:favicon.ico", &nsurl); - } - if (error != NSERROR_OK) { - NSLOG(netsurf, INFO, - "Unable to create default location url"); - return; - } - } else { - nsurl = nsurl_ref(link->href); - } - - if (link == NULL) { - NSLOG(netsurf, INFO, "fetching general favicon from '%s'", - nsurl_access(nsurl)); - } else { - NSLOG(netsurf, INFO, "fetching favicon rel:%s '%s'", - lwc_string_data(link->rel), nsurl_access(nsurl)); - } - - hlcache_handle_retrieve(nsurl, HLCACHE_RETRIEVE_SNIFF_TYPE, - nsref, NULL, browser_window_favicon_callback, - bw, NULL, CONTENT_IMAGE, &bw->favicon.loading); - - nsurl_unref(nsurl); -} - -/** - * window callback errorcode handling. - */ -static void -browser_window_callback_errorcode(hlcache_handle *c, - struct browser_window *bw, - nserror code) -{ - const char* message; - - message = messages_get_errorcode(code); - - browser_window_set_status(bw, message); - - /* Only warn the user about errors in top-level windows */ - if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { - guit->misc->warning(message, NULL); - } - - if (c == bw->loading_content) { - bw->loading_content = NULL; - } else if (c == bw->current_content) { - bw->current_content = NULL; - browser_window_remove_caret(bw, false); - } - - hlcache_handle_release(c); - - browser_window_stop_throbber(bw); -} - - -/** - * Handle meta http-equiv refresh time elapsing by loading a new page. - * - * \param p browser window to refresh with new page - */ -static void browser_window_refresh(void *p) -{ - struct browser_window *bw = p; - nsurl *url; - nsurl *refresh; - hlcache_handle *parent = NULL; - enum browser_window_nav_flags flags = BW_NAVIGATE_UNVERIFIABLE; - - assert(bw->current_content != NULL && - (content_get_status(bw->current_content) == - CONTENT_STATUS_READY || - content_get_status(bw->current_content) == - CONTENT_STATUS_DONE)); - - /* Ignore if the refresh URL has gone - * (may happen if a fetch error occurred) */ - refresh = content_get_refresh_url(bw->current_content); - if (refresh == NULL) - return; - - /* mark this content as invalid so it gets flushed from the cache */ - content_invalidate_reuse_data(bw->current_content); - - url = hlcache_handle_get_url(bw->current_content); - if ((url == NULL) || (nsurl_compare(url, refresh, NSURL_COMPLETE))) { - flags |= BW_NAVIGATE_HISTORY; - } - - /* Treat an (almost) immediate refresh in a top-level browser window as - * if it were an HTTP redirect, and thus make the resulting fetch - * verifiable. - * - * See fetchcache.c for why redirected fetches should be verifiable at - * all. - */ - if (bw->refresh_interval <= 100 && bw->parent == NULL) { - flags &= ~BW_NAVIGATE_UNVERIFIABLE; - } else { - parent = bw->current_content; - } - - browser_window_navigate(bw, - refresh, - url, - flags, - NULL, - NULL, - parent); - -} - - -/** - * Transfer the loading_content to a new download window. - */ - -static void browser_window_convert_to_download(struct browser_window *bw, - llcache_handle *stream) -{ - struct browser_window *root = browser_window_get_root(bw); - nserror error; - - assert(root != NULL); - - error = download_context_create(stream, root->window); - if (error != NSERROR_OK) { - llcache_handle_abort(stream); - llcache_handle_release(stream); - } - - /* remove content from browser window */ - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - - browser_window_stop_throbber(bw); -} - -/** - * handle message for content ready on browser window - */ -static nserror -browser_window_content_ready(struct browser_window *bw) -{ - int width, height; - nserror res = NSERROR_OK; - - /* close and release the current window content */ - if (bw->current_content != NULL) { - content_close(bw->current_content); - hlcache_handle_release(bw->current_content); - } - - bw->current_content = bw->loading_content; - bw->loading_content = NULL; - - /* Format the new content to the correct dimensions */ - browser_window_get_dimensions(bw, &width, &height, true); - content_reformat(bw->current_content, false, width, height); - - /* history */ - if (bw->history_add && bw->history) { - nsurl *url = hlcache_handle_get_url(bw->current_content); - - if (urldb_add_url(url)) { - urldb_set_url_title(url, content_get_title(bw->current_content)); - urldb_update_url_visit_data(url); - urldb_set_url_content_type(url, - content_get_type(bw->current_content)); - - /* This is safe as we've just added the URL */ - global_history_add(urldb_get_url(url)); - } - /** - * \todo Urldb / Thumbnails / Local history brokenness - * - * We add to local history after calling urldb_add_url rather - * than in the block above. If urldb_add_url fails (as it - * will for urls like "about:about", "about:config" etc), - * there would be no local history node, and later calls to - * history_update will either explode or overwrite the node - * for the previous URL. - * - * We call it after, rather than before urldb_add_url because - * history_add calls bitmap render, which tries to register - * the thumbnail with urldb. That thumbnail registration - * fails if the url doesn't exist in urldb already, and only - * urldb-registered thumbnails get freed. So if we called - * history_add before urldb_add_url we would leak thumbnails - * for all newly visited URLs. With the history_add call - * after, we only leak the thumbnails when urldb does not add - * the URL. - * - * Also, since browser_window_history_add can create a - * thumbnail (content_redraw), we need to do it after - * content_reformat. - */ - browser_window_history_add(bw, bw->current_content, bw->frag_id); - } - - browser_window_remove_caret(bw, false); - - if (bw->window != NULL) { - guit->window->new_content(bw->window); - - browser_window_refresh_url_bar(bw); - } - - /* new content; set scroll_to_top */ - browser_window_update(bw, true); - content_open(bw->current_content, bw, 0, 0); - browser_window_set_status(bw, content_get_status_message(bw->current_content)); - - /* frames */ - if ((content_get_type(bw->current_content) == CONTENT_HTML) && - (html_get_frameset(bw->current_content) != NULL)) { - res = browser_window_create_frameset(bw, html_get_frameset(bw->current_content)); - } - - if (content_get_type(bw->current_content) == CONTENT_HTML && - html_get_iframe(bw->current_content) != NULL) { - browser_window_create_iframes(bw, html_get_iframe(bw->current_content)); - } - - return res; -} - - -/** - * handle message for content done on browser window - */ -static nserror -browser_window_content_done(struct browser_window *bw) -{ - float sx, sy; - struct rect rect; - int scrollx; - int scrolly; - - if (bw->window == NULL) { - /* Updated browser window's scrollbars. */ - /** - * \todo update browser window scrollbars before CONTENT_MSG_DONE - */ - browser_window_reformat(bw, true, bw->width, bw->height); - browser_window_handle_scrollbars(bw); - } - - browser_window_update(bw, false); - browser_window_set_status(bw, content_get_status_message(bw->current_content)); - browser_window_stop_throbber(bw); - browser_window_update_favicon(bw->current_content, bw, NULL); - - if (browser_window_history_get_scroll(bw, &sx, &sy) == NSERROR_OK) { - scrollx = (int)((float)content_get_width(bw->current_content) * sx); - scrolly = (int)((float)content_get_height(bw->current_content) * sy); - rect.x0 = rect.x1 = scrollx; - rect.y0 = rect.y1 = scrolly; - if (browser_window_set_scroll(bw, &rect) != NSERROR_OK) { - NSLOG(netsurf, WARNING, - "Unable to set browser scroll offsets to %d by %d", - scrollx, scrolly); - } - } - - browser_window_history_update(bw, bw->current_content); - hotlist_update_url(hlcache_handle_get_url(bw->current_content)); - - if (bw->refresh_interval != -1) { - guit->misc->schedule(bw->refresh_interval * 10, - browser_window_refresh, bw); - } - - return NSERROR_OK; -} - -/** - * Browser window content event callback handler. - */ -static nserror -browser_window_callback(hlcache_handle *c, - const hlcache_event *event, - void *pw) -{ - struct browser_window *bw = pw; - nserror res = NSERROR_OK; - - switch (event->type) { - case CONTENT_MSG_LOG: - browser_window_console_log(bw, - event->data.log.src, - event->data.log.msg, - event->data.log.msglen, - event->data.log.flags); - break; - case CONTENT_MSG_DOWNLOAD: - assert(bw->loading_content == c); - - browser_window_convert_to_download(bw, event->data.download); - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - break; - - case CONTENT_MSG_LOADING: - assert(bw->loading_content == c); - -#ifdef WITH_THEME_INSTALL - if (content_get_type(c) == CONTENT_THEME) { - theme_install_start(c); - bw->loading_content = NULL; - browser_window_stop_throbber(bw); - } else -#endif - { - bw->refresh_interval = -1; - browser_window_set_status(bw, - content_get_status_message(c)); - } - break; - - case CONTENT_MSG_READY: - assert(bw->loading_content == c); - - res = browser_window_content_ready(bw); - break; - - case CONTENT_MSG_DONE: - assert(bw->current_content == c); - - res = browser_window_content_done(bw); - break; - - case CONTENT_MSG_ERRORCODE: - browser_window_callback_errorcode(c, bw, event->data.errorcode); - break; - - case CONTENT_MSG_ERROR: - browser_window_set_status(bw, event->data.error); - - /* Only warn the user about errors in top-level windows */ - if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { - guit->misc->warning(event->data.error, NULL); - } - - if (c == bw->loading_content) { - bw->loading_content = NULL; - } else if (c == bw->current_content) { - bw->current_content = NULL; - browser_window_remove_caret(bw, false); - } - - hlcache_handle_release(c); - - browser_window_stop_throbber(bw); - break; - - case CONTENT_MSG_REDIRECT: - if (urldb_add_url(event->data.redirect.from)) - urldb_update_url_visit_data(event->data.redirect.from); - break; - - case CONTENT_MSG_STATUS: - if (event->data.explicit_status_text == NULL) { - /* Object content's status text updated */ - const char *status = NULL; - if (bw->loading_content != NULL) - /* Give preference to any loading content */ - status = content_get_status_message( - bw->loading_content); - - if (status == NULL) - status = content_get_status_message(c); - - if (status != NULL) - browser_window_set_status(bw, status); - } else { - /* Object content wants to set explicit message */ - browser_window_set_status(bw, - event->data.explicit_status_text); - } - break; - - case CONTENT_MSG_REFORMAT: - if (c == bw->current_content && - content_get_type(c) == CONTENT_HTML) { - /* reposition frames */ - if (html_get_frameset(c) != NULL) - browser_window_recalculate_frameset(bw); - /* reflow iframe positions */ - if (html_get_iframe(c) != NULL) - browser_window_recalculate_iframes(bw); - } - - /* Hide any caret, but don't remove it */ - browser_window_remove_caret(bw, true); - - if (!(event->data.background)) { - /* Reformatted content should be redrawn */ - browser_window_update(bw, false); - } - break; - - case CONTENT_MSG_REDRAW: - { - struct rect rect = { - .x0 = event->data.redraw.x, - .y0 = event->data.redraw.y, - .x1 = event->data.redraw.x + event->data.redraw.width, - .y1 = event->data.redraw.y + event->data.redraw.height - }; - - browser_window_update_box(bw, &rect); - } - break; - - case CONTENT_MSG_REFRESH: - bw->refresh_interval = event->data.delay * 100; - break; - - case CONTENT_MSG_LINK: /* content has an rfc5988 link element */ - { - bool match; - - /* Handle "icon" and "shortcut icon" */ - if ((lwc_string_caseless_isequal( - event->data.rfc5988_link->rel, - corestring_lwc_icon, - &match) == lwc_error_ok && match) || - (lwc_string_caseless_isequal( - event->data.rfc5988_link->rel, - corestring_lwc_shortcut_icon, - &match) == lwc_error_ok && match)) { - /* it's a favicon perhaps start a fetch for it */ - browser_window_update_favicon(c, bw, - event->data.rfc5988_link); - } - } - break; - - case CONTENT_MSG_GETCTX: - /* only the content object created by the browser - * window requires a new global compartment object - */ - assert(bw->loading_content == c); - if (js_newcompartment(bw->jsctx, - bw, - hlcache_handle_get_content(c)) != NULL) { - *(event->data.jscontext) = bw->jsctx; - } - break; - - case CONTENT_MSG_GETDIMS: - { - int width; - int height; - - browser_window_get_dimensions(bw, &width, &height, true); - - *(event->data.getdims.viewport_width) = width; - *(event->data.getdims.viewport_height) = height; - break; - } - - case CONTENT_MSG_SCROLL: - { - struct rect rect = { - .x0 = event->data.scroll.x0, - .y0 = event->data.scroll.y0, - }; - - /* Content wants to be scrolled */ - if (bw->current_content != c) { - break; - } - - if (event->data.scroll.area) { - rect.x1 = event->data.scroll.x1; - rect.y1 = event->data.scroll.y1; - } else { - rect.x1 = event->data.scroll.x0; - rect.y1 = event->data.scroll.y0; - } - browser_window_set_scroll(bw, &rect); - - break; - } - - case CONTENT_MSG_DRAGSAVE: - { - /* Content wants drag save of a content */ - struct browser_window *root = browser_window_get_root(bw); - hlcache_handle *save = event->data.dragsave.content; - - if (save == NULL) { - save = c; - } - - switch(event->data.dragsave.type) { - case CONTENT_SAVE_ORIG: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_OBJECT_ORIG); - break; - - case CONTENT_SAVE_NATIVE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_OBJECT_NATIVE); - break; - - case CONTENT_SAVE_COMPLETE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_COMPLETE); - break; - - case CONTENT_SAVE_SOURCE: - guit->window->drag_save_object(root->window, save, - GUI_SAVE_SOURCE); - break; - } - } - break; - - case CONTENT_MSG_SAVELINK: - { - /* Content wants a link to be saved */ - struct browser_window *root = browser_window_get_root(bw); - guit->window->save_link(root->window, - event->data.savelink.url, - event->data.savelink.title); - } - break; - - case CONTENT_MSG_POINTER: - /* Content wants to have specific mouse pointer */ - browser_window_set_pointer(bw, event->data.pointer); - break; - - case CONTENT_MSG_DRAG: - { - browser_drag_type bdt = DRAGGING_NONE; - - switch (event->data.drag.type) { - case CONTENT_DRAG_NONE: - bdt = DRAGGING_NONE; - break; - case CONTENT_DRAG_SCROLL: - bdt = DRAGGING_CONTENT_SCROLLBAR; - break; - case CONTENT_DRAG_SELECTION: - bdt = DRAGGING_SELECTION; - break; - } - browser_window_set_drag_type(bw, bdt, event->data.drag.rect); - } - break; - - case CONTENT_MSG_CARET: - switch (event->data.caret.type) { - case CONTENT_CARET_REMOVE: - browser_window_remove_caret(bw, false); - break; - case CONTENT_CARET_HIDE: - browser_window_remove_caret(bw, true); - break; - case CONTENT_CARET_SET_POS: - browser_window_place_caret(bw, - event->data.caret.pos.x, - event->data.caret.pos.y, - event->data.caret.pos.height, - event->data.caret.pos.clip); - break; - } - break; - - case CONTENT_MSG_SELECTION: - browser_window_set_selection(bw, - event->data.selection.selection, - event->data.selection.read_only); - break; - - case CONTENT_MSG_SELECTMENU: - if (event->data.select_menu.gadget->type == GADGET_SELECT) { - struct browser_window *root = - browser_window_get_root(bw); - guit->window->create_form_select_menu(root->window, - event->data.select_menu.gadget); - } - - break; - - case CONTENT_MSG_GADGETCLICK: - if (event->data.gadget_click.gadget->type == GADGET_FILE) { - struct browser_window *root = - browser_window_get_root(bw); - guit->window->file_gadget_open(root->window, c, - event->data.gadget_click.gadget); - } - - break; - - default: - break; - } - - return res; -} - - -/* Have to forward declare browser_window_destroy_internal */ -static void browser_window_destroy_internal(struct browser_window *bw); - -/** - * Close and destroy all child browser window. - * - * \param bw browser window - */ -static void browser_window_destroy_children(struct browser_window *bw) -{ - int i; - - if (bw->children) { - for (i = 0; i < (bw->rows * bw->cols); i++) - browser_window_destroy_internal(&bw->children[i]); - free(bw->children); - bw->children = NULL; - bw->rows = 0; - bw->cols = 0; - } - if (bw->iframes) { - for (i = 0; i < bw->iframe_count; i++) - browser_window_destroy_internal(&bw->iframes[i]); - free(bw->iframes); - bw->iframes = NULL; - bw->iframe_count = 0; - } -} - - -/** - * internal scheduled reformat callback. - * - * scheduled reformat callback to allow reformats from unthreaded context. - * - * \param vbw The browser window to be reformatted - */ -static void scheduled_reformat(void *vbw) -{ - struct browser_window *bw = vbw; - int width; - int height; - nserror res; - - res = guit->window->get_dimensions(bw->window, &width, &height, false); - if (res == NSERROR_OK) { - browser_window_reformat(bw, false, width, height); - } -} - - -/** - * Release all memory associated with a browser window. - * - * \param bw browser window - */ -static void browser_window_destroy_internal(struct browser_window *bw) -{ - assert(bw); - - NSLOG(netsurf, INFO, "Destroying window"); - - if (bw->children != NULL || bw->iframes != NULL) { - browser_window_destroy_children(bw); - } - - /* Destroy scrollbars */ - if (bw->scroll_x != NULL) { - scrollbar_destroy(bw->scroll_x); - } - - if (bw->scroll_y != NULL) { - scrollbar_destroy(bw->scroll_y); - } - - /* clear any pending callbacks */ - guit->misc->schedule(-1, browser_window_refresh, bw); - /* The ugly cast here is so the reformat function can be - * passed a gui window pointer in its API rather than void* - */ - NSLOG(netsurf, INFO, - "Clearing reformat schedule for browser window %p", bw); - guit->misc->schedule(-1, scheduled_reformat, bw); - - /* If this brower window is not the root window, and has focus, unset - * the root browser window's focus pointer. */ - if (!bw->window) { - struct browser_window *top = browser_window_get_root(bw); - - if (top->focus == bw) - top->focus = top; - - if (top->selection.bw == bw) { - browser_window_set_selection(top, false, false); - } - } - - /* Destruction order is important: we must ensure that the frontend - * destroys any window(s) associated with this browser window before - * we attempt any destructive cleanup. - */ - - if (bw->window) { - /* Only the root window has a GUI window */ - guit->window->destroy(bw->window); - } - - if (bw->loading_content != NULL) { - hlcache_handle_abort(bw->loading_content); - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - } - - if (bw->current_content != NULL) { - content_close(bw->current_content); - hlcache_handle_release(bw->current_content); - bw->current_content = NULL; - } - - if (bw->favicon.loading != NULL) { - hlcache_handle_abort(bw->favicon.loading); - hlcache_handle_release(bw->favicon.loading); - bw->favicon.loading = NULL; - } - - if (bw->favicon.current != NULL) { - content_close(bw->favicon.current); - hlcache_handle_release(bw->favicon.current); - bw->favicon.current = NULL; - } - - if (bw->box != NULL) { - bw->box->iframe = NULL; - bw->box = NULL; - } - - if (bw->jsctx != NULL) { - js_destroycontext(bw->jsctx); - } - - /* These simply free memory, so are safe here */ - - if (bw->frag_id != NULL) { - lwc_string_unref(bw->frag_id); - } - - browser_window_history_destroy(bw); - - free(bw->name); - free(bw->status.text); - bw->status.text = NULL; - NSLOG(netsurf, INFO, "Status text cache match:miss %d:%d", - bw->status.match, bw->status.miss); -} - -/** - * Update URL bar for a given browser window to given URL - * - * \param bw Browser window to update URL bar for. - * \param url URL for content displayed by bw including any fragment. - */ -static inline nserror -browser_window_refresh_url_bar_internal(struct browser_window *bw, nsurl *url) -{ - assert(bw); - assert(url); - - if ((bw->parent != NULL) || (bw->window == NULL)) { - /* Not root window or no gui window so do not set a URL */ - return NSERROR_OK; - } - - return guit->window->set_url(bw->window, url); -} - - -/* exported interface, documented in netsurf/browser_window.h */ -void browser_window_destroy(struct browser_window *bw) -{ - /* can't destoy child windows on their own */ - assert(!bw->parent); - - /* destroy */ - browser_window_destroy_internal(bw); - free(bw); -} - -/* exported interface, documented in netsurf/browser_window.h */ -nserror browser_window_refresh_url_bar(struct browser_window *bw) -{ - nserror ret; - nsurl *display_url; - - assert(bw); - - if (bw->parent != NULL) { - /* Not root window; don't set a URL in GUI URL bar */ - return NSERROR_OK; - } - - if (bw->current_content == NULL) { - /* no content so return about:blank */ - ret = browser_window_refresh_url_bar_internal(bw, - corestring_nsurl_about_blank); - } else if (bw->frag_id == NULL) { - ret = browser_window_refresh_url_bar_internal(bw, - hlcache_handle_get_url(bw->current_content)); - } else { - /* Combine URL and Fragment */ - ret = nsurl_refragment( - hlcache_handle_get_url(bw->current_content), - bw->frag_id, &display_url); - if (ret == NSERROR_OK) { - ret = browser_window_refresh_url_bar_internal(bw, - display_url); - nsurl_unref(display_url); - } - } - - return ret; -} - - -/* exported interface documented in netsurf/browser_window.h */ -nserror -browser_window_navigate(struct browser_window *bw, - nsurl *url, - nsurl *referrer, - enum browser_window_nav_flags flags, - char *post_urlenc, - struct fetch_multipart_data *post_multipart, - hlcache_handle *parent) -{ - hlcache_handle *c; - int depth = 0; - struct browser_window *cur; - uint32_t fetch_flags = 0; - bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); - llcache_post_data post; - hlcache_child_context child; - nserror error; - - assert(bw); - assert(url); - - NSLOG(netsurf, INFO, "bw %p, url %s", bw, nsurl_access(url)); - - /* If we're navigating and we have a history entry and a content - * then update the history entry before we navigate to save our - * current state. However since history navigation pre-moves - * the history state, we ensure that we only do this if we've not - * been suppressed. In the suppressed case, the history code - * updates the history itself before navigating. - */ - if (bw->current_content != NULL && - bw->history != NULL && - bw->history->current != NULL && - !(flags & BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE)) { - browser_window_history_update(bw, bw->current_content); - } - - /* don't allow massively nested framesets */ - for (cur = bw; cur->parent; cur = cur->parent) { - depth++; - } - if (depth > FRAME_DEPTH) { - NSLOG(netsurf, INFO, "frame depth too high."); - return NSERROR_FRAME_DEPTH; - } - - /* Set up retrieval parameters */ - if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { - fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; - } - - if (post_multipart != NULL) { - post.type = LLCACHE_POST_MULTIPART; - post.data.multipart = post_multipart; - } else if (post_urlenc != NULL) { - post.type = LLCACHE_POST_URL_ENCODED; - post.data.urlenc = post_urlenc; - } - - child.charset = content_get_encoding(parent, CONTENT_ENCODING_NORMAL); - if ((parent != NULL) && (content_get_type(parent) == CONTENT_HTML)) { - child.quirks = content_get_quirks(parent); - } - - url = nsurl_ref(url); - - if (referrer != NULL) { - referrer = nsurl_ref(referrer); - } - - /* Get download out of the way */ - if ((flags & BW_NAVIGATE_DOWNLOAD) != 0) { - error = browser_window_download(bw, - url, - referrer, - fetch_flags, - fetch_is_post, - &post); - nsurl_unref(url); - if (referrer != NULL) { - nsurl_unref(referrer); - } - return error; - } - - if (bw->frag_id != NULL) { - lwc_string_unref(bw->frag_id); - } - bw->frag_id = NULL; - - if (nsurl_has_component(url, NSURL_FRAGMENT)) { - bool same_url = false; - - bw->frag_id = nsurl_get_component(url, NSURL_FRAGMENT); - - /* Compare new URL with existing one (ignoring fragments) */ - if ((bw->current_content != NULL) && - (hlcache_handle_get_url(bw->current_content) != NULL)) { - same_url = nsurl_compare(url, - hlcache_handle_get_url( - bw->current_content), - NSURL_COMPLETE); - } - - /* if we're simply moving to another ID on the same page, - * don't bother to fetch, just update the window. - */ - if ((same_url) && - (fetch_is_post == false) && - (nsurl_has_component(url, NSURL_QUERY) == false)) { - nsurl_unref(url); - - if (referrer != NULL) { - nsurl_unref(referrer); - } - - if ((flags & BW_NAVIGATE_HISTORY) != 0) { - browser_window_history_add(bw, - bw->current_content, bw->frag_id); - } - - browser_window_update(bw, false); - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - return NSERROR_OK; - } - } - - browser_window_stop(bw); - browser_window_remove_caret(bw, false); - browser_window_destroy_children(bw); - - NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(url)); - - browser_window_set_status(bw, messages_get("Loading")); - bw->history_add = (flags & BW_NAVIGATE_HISTORY); - - /* Verifiable fetches may trigger a download */ - if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { - fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD; - } - - error = hlcache_handle_retrieve(url, - fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE, - referrer, - fetch_is_post ? &post : NULL, - browser_window_callback, bw, - parent != NULL ? &child : NULL, - CONTENT_ANY, &c); - - switch (error) { - case NSERROR_OK: - bw->loading_content = c; - browser_window_start_throbber(bw); - error = browser_window_refresh_url_bar_internal(bw, url); - break; - - case NSERROR_NO_FETCH_HANDLER: /* no handler for this type */ - /** \todo does this always try and download even - * unverifiable content? - */ - error = guit->misc->launch_url(url); - break; - - default: /* report error to user */ - browser_window_set_status(bw, messages_get_errorcode(error)); - /** @todo should the caller report the error? */ - guit->misc->warning(messages_get_errorcode(error), NULL); - break; - - } - - nsurl_unref(url); - if (referrer != NULL) { - nsurl_unref(referrer); - } - - /* Record time */ - nsu_getmonotonic_ms(&bw->last_action); - - return error; -} - - -/* Exported interface, documented in browser.h */ -bool browser_window_up_available(struct browser_window *bw) -{ - bool result = false; - - if (bw != NULL && bw->current_content != NULL) { - nsurl *parent; - nserror err = nsurl_parent(hlcache_handle_get_url( - bw->current_content), &parent); - if (err == NSERROR_OK) { - result = nsurl_compare(hlcache_handle_get_url( - bw->current_content), parent, - NSURL_COMPLETE) == false; - nsurl_unref(parent); - } - } - - return result; -} - - -/* Exported interface, documented in browser.h */ -nserror browser_window_navigate_up(struct browser_window *bw, bool new_window) -{ - nsurl *current, *parent; - nserror err; - - if (bw == NULL) - return NSERROR_BAD_PARAMETER; - - current = browser_window_access_url(bw); - - err = nsurl_parent(current, &parent); - if (err != NSERROR_OK) { - return err; - } - - if (nsurl_compare(current, parent, NSURL_COMPLETE) == true) { - /* Can't go up to parent from here */ - nsurl_unref(parent); - return NSERROR_OK; - } - - if (new_window) { - err = browser_window_create(BW_CREATE_CLONE, - parent, NULL, bw, NULL); - } else { - err = browser_window_navigate(bw, parent, NULL, - BW_NAVIGATE_HISTORY, NULL, NULL, NULL); - } - - nsurl_unref(parent); - return err; -} - - -/* Exported interface, documented in include/netsurf/browser_window.h */ -nsurl* browser_window_access_url(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content != NULL) { - return hlcache_handle_get_url(bw->current_content); - - } else if (bw->loading_content != NULL) { - /* TODO: should we return this? */ - return hlcache_handle_get_url(bw->loading_content); - } - - return corestring_nsurl_about_blank; -} - -/* Exported interface, documented in include/netsurf/browser_window.h */ -nserror browser_window_get_url( - struct browser_window *bw, - bool fragment, - nsurl** url_out) -{ - nserror err; - nsurl *url; - - assert(bw != NULL); - - if (!fragment || bw->frag_id == NULL || bw->loading_content != NULL) { - /* If there's a loading content, then the bw->frag_id will have - * been trampled, possibly with a new frag_id, but we will - * still be returning the current URL, so in this edge case - * we just drop any fragment. */ - url = nsurl_ref(browser_window_access_url(bw)); - - } else { - err = nsurl_refragment(browser_window_access_url(bw), - bw->frag_id, &url); - if (err != NSERROR_OK) { - return err; - } - } - - *url_out = url; - return NSERROR_OK; -} - -/* Exported interface, documented in browser.h */ -const char* browser_window_get_title(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content != NULL) { - return content_get_title(bw->current_content); - } - - /* no content so return about:blank */ - return nsurl_access(corestring_nsurl_about_blank); -} - -/* Exported interface, documented in browser.h */ -struct history * browser_window_get_history(struct browser_window *bw) -{ - assert(bw != NULL); - - return bw->history; -} - - -/* Exported interface, documented in browser.h */ -bool browser_window_has_content(struct browser_window *bw) -{ - assert(bw != NULL); - - if (bw->current_content == NULL) { - return false; - } - - return true; -} - -/* Exported interface, documented in browser.h */ -struct hlcache_handle *browser_window_get_content(struct browser_window *bw) -{ - return bw->current_content; -} - -/* Exported interface, documented in browser.h */ -nserror browser_window_get_extents(struct browser_window *bw, bool scaled, - int *width, int *height) -{ - assert(bw != NULL); - - if (bw->current_content == NULL) { - *width = 0; - *height = 0; - return NSERROR_BAD_CONTENT; - } - - *width = content_get_width(bw->current_content); - *height = content_get_height(bw->current_content); - - if (scaled) { - *width *= bw->scale; - *height *= bw->scale; - } - - return NSERROR_OK; -} - - -/* exported internal interface, documented in desktop/browser_private.h */ -void browser_window_get_dimensions(struct browser_window *bw, - int *width, int *height, bool scaled) -{ - assert(bw); - - if (bw->window == NULL) { - /* Core managed browser window */ - *width = bw->width; - *height = bw->height; - } else { - /* Front end window */ - guit->window->get_dimensions(bw->window, width, height, scaled); - } -} - - -/* Exported interface, documented in browser.h */ -void browser_window_set_dimensions(struct browser_window *bw, - int width, int height) -{ - assert(bw); - - if (bw->window == NULL) { - /* Core managed browser window */ - bw->width = width; - bw->height = height; - } else { - NSLOG(netsurf, INFO, - "Asked to set dimensions of front end window."); - assert(0); - } -} - - -/** - * scroll to a fragment if present - * - * \param bw browser window - * \return true if the scroll was sucessful - */ -static bool frag_scroll(struct browser_window *bw) -{ - struct rect rect; - - if (bw->frag_id == NULL) { - return false; - } - - if (!html_get_id_offset(bw->current_content, - bw->frag_id, - &rect.x0, - &rect.y0)) { - return false; - } - - rect.x1 = rect.x0; - rect.y1 = rect.y0; - if (browser_window_set_scroll(bw, &rect) == NSERROR_OK) { - if (bw->current_content != NULL && - bw->history != NULL && - bw->history->current != NULL) { - browser_window_history_update(bw, bw->current_content); - } - return true; - } - return false; -} - -/* Exported interface, documented in browser.h */ -void browser_window_update(struct browser_window *bw, bool scroll_to_top) -{ - static const struct rect zrect = { - .x0 = 0, - .y0 = 0, - .x1 = 0, - .y1 = 0 - }; - - if (bw->current_content == NULL) { - return; - } - - switch (bw->browser_window_type) { - - case BROWSER_WINDOW_NORMAL: - /* Root browser window, constituting a front end window/tab */ - guit->window->set_title(bw->window, - content_get_title(bw->current_content)); - - browser_window_update_extent(bw); - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - if (!frag_scroll(bw)) { - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - } - - guit->window->invalidate(bw->window, NULL); - - break; - - case BROWSER_WINDOW_IFRAME: - /* Internal iframe browser window */ - assert(bw->parent != NULL); - assert(bw->parent->current_content != NULL); - - browser_window_update_extent(bw); - - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - frag_scroll(bw); - - html_redraw_a_box(bw->parent->current_content, bw->box); - break; - - case BROWSER_WINDOW_FRAME: - { - struct rect rect; - browser_window_update_extent(bw); - - if (scroll_to_top) { - browser_window_set_scroll(bw, &zrect); - } - - /* if frag_id exists, then try to scroll to it */ - /** @todo don't do this if the user has scrolled */ - frag_scroll(bw); - - rect.x0 = scrollbar_get_offset(bw->scroll_x); - rect.y0 = scrollbar_get_offset(bw->scroll_y); - rect.x1 = rect.x0 + bw->width; - rect.y1 = rect.y0 + bw->height; - - browser_window_update_box(bw, &rect); - } - break; - - default: - case BROWSER_WINDOW_FRAMESET: - /* Nothing to do */ - break; - } -} - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_update_box(struct browser_window *bw, struct rect *rect) -{ - int pos_x; - int pos_y; - struct browser_window *top; - - assert(bw); - - if (bw->window != NULL) { - /* Front end window */ - guit->window->invalidate(bw->window, rect); - } else { - /* Core managed browser window */ - browser_window_get_position(bw, true, &pos_x, &pos_y); - - top = browser_window_get_root(bw); - - rect->x0 += pos_x / bw->scale; - rect->y0 += pos_y / bw->scale; - rect->x1 += pos_x / bw->scale; - rect->y1 += pos_y / bw->scale; - - guit->window->invalidate(top->window, rect); - } -} - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_stop(struct browser_window *bw) -{ - int children, index; - - if (bw->loading_content != NULL) { - hlcache_handle_abort(bw->loading_content); - hlcache_handle_release(bw->loading_content); - bw->loading_content = NULL; - } - - if (bw->current_content != NULL && content_get_status( - bw->current_content) != CONTENT_STATUS_DONE) { - nserror error; - assert(content_get_status(bw->current_content) == - CONTENT_STATUS_READY); - error = hlcache_handle_abort(bw->current_content); - assert(error == NSERROR_OK); - } - - guit->misc->schedule(-1, browser_window_refresh, bw); - - if (bw->children) { - children = bw->rows * bw->cols; - for (index = 0; index < children; index++) - browser_window_stop(&bw->children[index]); - } - if (bw->iframes) { - children = bw->iframe_count; - for (index = 0; index < children; index++) - browser_window_stop(&bw->iframes[index]); - } - - if (bw->current_content != NULL) { - browser_window_refresh_url_bar(bw); - } - - browser_window_stop_throbber(bw); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_reload(struct browser_window *bw, bool all) -{ - hlcache_handle *c; - unsigned int i; - - if (bw->current_content == NULL || bw->loading_content != NULL) - return; - - if (all && content_get_type(bw->current_content) == CONTENT_HTML) { - struct html_stylesheet *sheets; - struct content_html_object *object; - unsigned int count; - - c = bw->current_content; - - /* invalidate objects */ - object = html_get_objects(c, &count); - - for (; object != NULL; object = object->next) { - if (object->content != NULL) - content_invalidate_reuse_data(object->content); - } - - /* invalidate stylesheets */ - sheets = html_get_stylesheets(c, &count); - - for (i = STYLESHEET_START; i != count; i++) { - if (sheets[i].sheet != NULL) { - content_invalidate_reuse_data(sheets[i].sheet); - } - } - } - - content_invalidate_reuse_data(bw->current_content); - - browser_window_navigate(bw, - hlcache_handle_get_url(bw->current_content), - NULL, - BW_NAVIGATE_NONE, - NULL, - NULL, - NULL); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_status(struct browser_window *bw, const char *text) -{ - int text_len; - /* find topmost window */ - while (bw->parent) - bw = bw->parent; - - if ((bw->status.text != NULL) && - (strcmp(text, bw->status.text) == 0)) { - /* status text is unchanged */ - bw->status.match++; - return; - } - - /* status text is changed */ - - text_len = strlen(text); - - if ((bw->status.text == NULL) || (bw->status.text_len < text_len)) { - /* no current string allocation or it is not long enough */ - free(bw->status.text); - bw->status.text = strdup(text); - bw->status.text_len = text_len; - } else { - /* current allocation has enough space */ - memcpy(bw->status.text, text, text_len + 1); - } - - bw->status.miss++; - guit->window->set_status(bw->window, bw->status.text); -} - - -/* Exported interface, documented in netsurf/browser_window.h */ -void browser_window_set_pointer(struct browser_window *bw, - browser_pointer_shape shape) -{ - struct browser_window *root = browser_window_get_root(bw); - gui_pointer_shape gui_shape; - bool loading; - uint64_t ms_now; - - assert(root); - assert(root->window); - - loading = ((bw->loading_content != NULL) || - ((bw->current_content != NULL) && - (content_get_status(bw->current_content) == CONTENT_STATUS_READY))); - - nsu_getmonotonic_ms(&ms_now); - - if (loading && ((ms_now - bw->last_action) < 1000)) { - /* If loading and less than 1 second since last link followed, - * force progress indicator pointer */ - gui_shape = GUI_POINTER_PROGRESS; - - } else if (shape == BROWSER_POINTER_AUTO) { - /* Up to browser window to decide */ - if (loading) { - gui_shape = GUI_POINTER_PROGRESS; - } else { - gui_shape = GUI_POINTER_DEFAULT; - } - - } else { - /* Use what we were told */ - gui_shape = (gui_pointer_shape)shape; - } - - guit->window->set_pointer(root->window, gui_shape); -} - - -/* exported function documented in netsurf/browser_window.h */ -nserror browser_window_schedule_reformat(struct browser_window *bw) -{ - if (bw->window == NULL) { - return NSERROR_BAD_PARAMETER; - } - - guit->misc->schedule(0, scheduled_reformat, bw); - - return NSERROR_OK; -} - - -/* exported function documented in netsurf/browser_window.h */ -void browser_window_reformat(struct browser_window *bw, bool background, - int width, int height) -{ - hlcache_handle *c = bw->current_content; - - if (c == NULL) - return; - - if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) { - /* Iframe dimensions are already scaled in parent's layout */ - width /= bw->scale; - height /= bw->scale; - } - - if (bw->window == NULL) { - /* Core managed browser window; subtract scrollbar width */ - width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0; - height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0; - - width = width > 0 ? width : 0; - height = height > 0 ? height : 0; - } - - content_reformat(c, background, width, height); -} - -/** - * Set browser window scale. - * - * \param bw Browser window. - * \param scale value. - */ -static void browser_window_set_scale_internal(struct browser_window *bw, - float scale) -{ - int i; - hlcache_handle *c; - - if (fabs(bw->scale-scale) < 0.0001) - return; - - bw->scale = scale; - c = bw->current_content; - - if (c != NULL) { - if (content_can_reformat(c) == false) { - browser_window_update(bw, false); - } else { - browser_window_schedule_reformat(bw); - } - } - - for (i = 0; i < (bw->cols * bw->rows); i++) - browser_window_set_scale_internal(&bw->children[i], scale); - for (i = 0; i < bw->iframe_count; i++) - browser_window_set_scale_internal(&bw->iframes[i], scale); -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_set_scale(struct browser_window *bw, float scale, bool all) -{ - while (bw->parent && all) - bw = bw->parent; - - browser_window_set_scale_internal(bw, scale); - - if (bw->parent) - bw = bw->parent; - - browser_window_recalculate_frameset(bw); -} - - -/* exported interface documented in netsurf/browser_window.h */ -float browser_window_get_scale(struct browser_window *bw) -{ - if (bw == NULL) { - return 1.0; - } - - return bw->scale; -} - -/** - * Find browser window. - * - * \param bw Browser window. - * \param target Name of target. - * \param depth Depth to scan. - * \param page The browser window page. - * \param rdepth The rdepth. - * \param bw_target the output browser window. - */ -static void browser_window_find_target_internal(struct browser_window *bw, - const char *target, int depth, struct browser_window *page, - int *rdepth, struct browser_window **bw_target) -{ - int i; - - if ((bw->name) && (!strcasecmp(bw->name, target))) { - if ((bw == page) || (depth > *rdepth)) { - *rdepth = depth; - *bw_target = bw; - } - } - - if ((!bw->children) && (!bw->iframes)) - return; - - depth++; - - if (bw->children != NULL) { - for (i = 0; i < (bw->cols * bw->rows); i++) { - if ((bw->children[i].name) && - (!strcasecmp(bw->children[i].name, - target))) { - if ((page == &bw->children[i]) || - (depth > *rdepth)) { - *rdepth = depth; - *bw_target = &bw->children[i]; - } - } - if (bw->children[i].children) - browser_window_find_target_internal( - &bw->children[i], - target, depth, page, - rdepth, bw_target); - } - } - - if (bw->iframes != NULL) { - for (i = 0; i < bw->iframe_count; i++) - browser_window_find_target_internal(&bw->iframes[i], - target, depth, page, rdepth, bw_target); - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -struct browser_window *browser_window_find_target(struct browser_window *bw, - const char *target, browser_mouse_state mouse) -{ - struct browser_window *bw_target; - struct browser_window *top; - hlcache_handle *c; - int rdepth; - nserror error; - - /* use the base target if we don't have one */ - c = bw->current_content; - if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML) - target = html_get_base_target(c); - if (target == NULL) - target = TARGET_SELF; - - /* allow the simple case of target="_blank" to be ignored if requested - */ - if ((!(mouse & BROWSER_MOUSE_CLICK_2)) && - (!((mouse & BROWSER_MOUSE_CLICK_2) && - (mouse & BROWSER_MOUSE_MOD_2))) && - (!nsoption_bool(target_blank))) { - /* not a mouse button 2 click - * not a mouse button 1 click with ctrl pressed - * configured to ignore target="_blank" */ - if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))) - return bw; - } - - /* handle reserved keywords */ - if (((nsoption_bool(button_2_tab)) && - (mouse & BROWSER_MOUSE_CLICK_2))|| - ((!nsoption_bool(button_2_tab)) && - ((mouse & BROWSER_MOUSE_CLICK_1) && - (mouse & BROWSER_MOUSE_MOD_2))) || - ((nsoption_bool(button_2_tab)) && - ((target == TARGET_BLANK) || - (!strcasecmp(target, "_blank"))))) { - /* open in new tab if: - * - button_2 opens in new tab and button_2 was pressed - * OR - * - button_2 doesn't open in new tabs and button_1 was - * pressed with ctrl held - * OR - * - button_2 opens in new tab and the link target is "_blank" - */ - error = browser_window_create(BW_CREATE_TAB | - BW_CREATE_HISTORY | - BW_CREATE_CLONE, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - return bw_target; - } else if (((!nsoption_bool(button_2_tab)) && - (mouse & BROWSER_MOUSE_CLICK_2)) || - ((nsoption_bool(button_2_tab)) && - ((mouse & BROWSER_MOUSE_CLICK_1) && - (mouse & BROWSER_MOUSE_MOD_2))) || - ((!nsoption_bool(button_2_tab)) && - ((target == TARGET_BLANK) || - (!strcasecmp(target, "_blank"))))) { - /* open in new window if: - * - button_2 doesn't open in new tabs and button_2 was pressed - * OR - * - button_2 opens in new tab and button_1 was pressed with - * ctrl held - * OR - * - button_2 doesn't open in new tabs and the link target is - * "_blank" - */ - error = browser_window_create(BW_CREATE_HISTORY | - BW_CREATE_CLONE, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - return bw_target; - } else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) { - return bw; - } else if ((target == TARGET_PARENT) || - (!strcasecmp(target, "_parent"))) { - if (bw->parent) - return bw->parent; - return bw; - } else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) { - while (bw->parent) - bw = bw->parent; - return bw; - } - - /* find frame according to B.8, ie using the following priorities: - * - * 1) current frame - * 2) closest to front - */ - rdepth = -1; - bw_target = NULL; - for (top = bw; top->parent; top = top->parent); - browser_window_find_target_internal(top, target, 0, bw, &rdepth, - &bw_target); - if (bw_target) - return bw_target; - - /* we require a new window using the target name */ - if (!nsoption_bool(target_blank)) - return bw; - - error = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY, - NULL, - NULL, - bw, - &bw_target); - if (error != NSERROR_OK) { - return bw; - } - - /* frame names should begin with an alphabetic character (a-z,A-Z), - * however in practice you get things such as '_new' and '2left'. The - * only real effect this has is when giving out names as it can be - * assumed that an author intended '_new' to create a new nameless - * window (ie '_blank') whereas in the case of '2left' the intention - * was for a new named window. As such we merely special case windows - * that begin with an underscore. */ - if (target[0] != '_') { - bw_target->name = strdup(target); - if (!bw_target->name) - guit->misc->warning("NoMemory", NULL); - } - return bw_target; -} - - -/** - * Handles the end of a drag operation in a browser window. - * - * \param bw browser window - * \param mouse state of mouse buttons and modifier keys - * \param x coordinate of mouse - * \param y coordinate of mouse - * - * \todo Remove this function, once these things are associated with content, - * rather than bw. - */ -static void browser_window_mouse_drag_end(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - int scr_x, scr_y; - - switch (bw->drag.type) { - case DRAGGING_SELECTION: - case DRAGGING_OTHER: - case DRAGGING_CONTENT_SCROLLBAR: - /* Drag handled by content handler */ - break; - - case DRAGGING_SCR_X: - - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y); - - bw->drag.type = DRAGGING_NONE; - break; - - case DRAGGING_SCR_Y: - - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y); - - bw->drag.type = DRAGGING_NONE; - break; - - default: - browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); - break; - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_mouse_track(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - hlcache_handle *c = bw->current_content; - const char *status = NULL; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - - if (bw->window != NULL && bw->drag.window && bw != bw->drag.window) { - /* This is the root browser window and there's an active drag - * in a sub window. - * Pass the mouse action straight on to that bw. */ - struct browser_window *drag_bw = bw->drag.window; - int off_x = 0; - int off_y = 0; - - browser_window_get_position(drag_bw, true, &off_x, &off_y); - - if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) { - browser_window_mouse_track(drag_bw, mouse, - x - off_x, y - off_y); - - } else if (drag_bw->browser_window_type == - BROWSER_WINDOW_IFRAME) { - browser_window_mouse_track(drag_bw, mouse, - x - off_x / bw->scale, - y - off_y / bw->scale); - } - return; - } - - if (bw->children) { - /* Browser window has children (frames) */ - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - for (cur_child = 0; cur_child < children; cur_child++) { - - child = &bw->children[cur_child]; - - if (x < child->x || y < child->y || - child->x + child->width < x || - child->y + child->height < y) { - /* Click not in this child */ - continue; - } - - /* It's this child that contains the mouse; pass - * mouse action on to child */ - browser_window_mouse_track(child, mouse, - x - child->x + scrollbar_get_offset( - child->scroll_x), - y - child->y + scrollbar_get_offset( - child->scroll_y)); - - /* Mouse action was for this child, we're done */ - return; - } - - /* Odd if we reached here, but nothing else can use the click - * when there are children. */ - return; - } - - if (c == NULL && bw->drag.type != DRAGGING_FRAME) { - return; - } - - if (bw->drag.type != DRAGGING_NONE && !mouse) { - browser_window_mouse_drag_end(bw, mouse, x, y); - } - - /* Browser window's horizontal scrollbar */ - if (bw->scroll_x != NULL && bw->drag.type != DRAGGING_SCR_Y) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if ((bw->drag.type == DRAGGING_SCR_X) || - (scr_x > 0 && - scr_x < browser_window_get_scrollbar_len(bw, true) && - scr_y > 0 && - scr_y < SCROLLBAR_WIDTH && - bw->drag.type == DRAGGING_NONE)) { - /* Start a scrollbar drag, or continue existing drag */ - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_x, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) { - browser_window_set_status(bw, status); - } - - browser_window_set_pointer(bw, pointer); - return; - } - } - - /* Browser window's vertical scrollbar */ - if (bw->scroll_y != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if ((bw->drag.type == DRAGGING_SCR_Y) || - (scr_y > 0 && - scr_y < browser_window_get_scrollbar_len(bw, false) && - scr_x > 0 && - scr_x < SCROLLBAR_WIDTH && - bw->drag.type == DRAGGING_NONE)) { - /* Start a scrollbar drag, or continue existing drag */ - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_y, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) { - browser_window_set_status(bw, status); - } - - browser_window_set_pointer(bw, pointer); - return; - } - } - - if (bw->drag.type == DRAGGING_FRAME) { - browser_window_resize_frame(bw, bw->x + x, bw->y + y); - } else if (bw->drag.type == DRAGGING_PAGE_SCROLL) { - /* mouse movement since drag started */ - struct rect rect; - - rect.x0 = bw->drag.start_x - x; - rect.y0 = bw->drag.start_y - y; - - /* new scroll offsets */ - rect.x0 += bw->drag.start_scroll_x; - rect.y0 += bw->drag.start_scroll_y; - - bw->drag.start_scroll_x = rect.x1 = rect.x0; - bw->drag.start_scroll_y = rect.y1 = rect.y0; - - browser_window_set_scroll(bw, &rect); - } else { - assert(c != NULL); - content_mouse_track(c, bw, mouse, x, y); - } -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_mouse_click(struct browser_window *bw, - browser_mouse_state mouse, int x, int y) -{ - hlcache_handle *c = bw->current_content; - const char *status = NULL; - browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; - - if (bw->children) { - /* Browser window has children (frames) */ - struct browser_window *child; - int cur_child; - int children = bw->rows * bw->cols; - - for (cur_child = 0; cur_child < children; cur_child++) { - - child = &bw->children[cur_child]; - - if (x < child->x || y < child->y || - child->x + child->width < x || - child->y + child->height < y) { - /* Click not in this child */ - continue; - } - - /* It's this child that contains the click; pass it - * on to child. */ - browser_window_mouse_click(child, mouse, - x - child->x + scrollbar_get_offset( - child->scroll_x), - y - child->y + scrollbar_get_offset( - child->scroll_y)); - - /* Mouse action was for this child, we're done */ - return; - } - - return; - } - - if (!c) - return; - - if (bw->scroll_x != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if (scr_x > 0 && scr_x < browser_window_get_scrollbar_len(bw, - true) && - scr_y > 0 && scr_y < SCROLLBAR_WIDTH) { - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_x, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) - browser_window_set_status(bw, status); - - browser_window_set_pointer(bw, pointer); - return; - } - } - - if (bw->scroll_y != NULL) { - int scr_x, scr_y; - browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); - scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); - scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); - - if (scr_y > 0 && scr_y < browser_window_get_scrollbar_len(bw, - false) && - scr_x > 0 && scr_x < SCROLLBAR_WIDTH) { - status = scrollbar_mouse_status_to_message( - scrollbar_mouse_action( - bw->scroll_y, mouse, - scr_x, scr_y)); - pointer = BROWSER_POINTER_DEFAULT; - - if (status != NULL) - browser_window_set_status(bw, status); - - browser_window_set_pointer(bw, pointer); - return; - } - } - - switch (content_get_type(c)) { - case CONTENT_HTML: - case CONTENT_TEXTPLAIN: - { - /* Give bw focus */ - struct browser_window *root_bw = browser_window_get_root(bw); - if (bw != root_bw->focus) { - browser_window_remove_caret(bw, false); - browser_window_set_selection(bw, false, true); - root_bw->focus = bw; - } - - /* Pass mouse action to content */ - content_mouse_action(c, bw, mouse, x, y); - } - break; - default: - if (mouse & BROWSER_MOUSE_MOD_2) { - if (mouse & BROWSER_MOUSE_DRAG_2) { - guit->window->drag_save_object(bw->window, c, - GUI_SAVE_OBJECT_NATIVE); - } else if (mouse & BROWSER_MOUSE_DRAG_1) { - guit->window->drag_save_object(bw->window, c, - GUI_SAVE_OBJECT_ORIG); - } - } else if (mouse & (BROWSER_MOUSE_DRAG_1 | - BROWSER_MOUSE_DRAG_2)) { - browser_window_page_drag_start(bw, x, y); - browser_window_set_pointer(bw, BROWSER_POINTER_MOVE); - } - break; - } -} - - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_redraw_rect(struct browser_window *bw, int x, int y, - int width, int height) -{ - content_request_redraw(bw->current_content, x, y, width, height); -} - - -/* exported interface documented in netsurf/browser_window.h */ -void browser_window_page_drag_start(struct browser_window *bw, int x, int y) -{ - assert(bw != NULL); - - browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL, NULL); - - bw->drag.start_x = x; - bw->drag.start_y = y; - - if (bw->window != NULL) { - /* Front end window */ - guit->window->get_scroll(bw->window, - &bw->drag.start_scroll_x, - &bw->drag.start_scroll_y); - - guit->window->scroll_start(bw->window); - } else { - /* Core managed browser window */ - bw->drag.start_scroll_x = scrollbar_get_offset(bw->scroll_x); - bw->drag.start_scroll_y = scrollbar_get_offset(bw->scroll_y); - } -} - - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_back_available(struct browser_window *bw) -{ - return (bw && bw->history && - browser_window_history_back_available(bw)); -} - - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_forward_available(struct browser_window *bw) -{ - return (bw && bw->history && - browser_window_history_forward_available(bw)); -} - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_reload_available(struct browser_window *bw) -{ - return (bw && bw->current_content && !bw->loading_content); -} - - -/* exported interface documented in netsurf/browser_window.h */ -bool browser_window_stop_available(struct browser_window *bw) -{ - return (bw && (bw->loading_content || - (bw->current_content && - (content_get_status(bw->current_content) != - CONTENT_STATUS_DONE)))); -} +#include "utils/errors.h" +#include "netsurf/browser.h" +#include "css/utils.h" -/* exported interface documented in browser.h */ +/* exported interface documented in netsurf/browser.h */ nserror browser_set_dpi(int dpi) { nscss_screen_dpi = INTTOFIX(dpi); @@ -3425,80 +34,8 @@ nserror browser_set_dpi(int dpi) return NSERROR_OK; } -/* exported interface documented in browser.h */ +/* exported interface documented in netsurf/browser.h */ int browser_get_dpi(void) { return FIXTOINT(nscss_screen_dpi); } - -/* exported interface documented in browser.h */ -bool browser_window_exec(struct browser_window *bw, const char *src, size_t srclen) -{ - assert(bw != NULL); - - if (!bw->current_content) { - NSLOG(netsurf, DEEPDEBUG, "Unable to exec, no content"); - return false; - } - - if (content_get_status(bw->current_content) != CONTENT_STATUS_DONE) { - NSLOG(netsurf, DEEPDEBUG, "Unable to exec, content not done"); - return false; - } - - /* Okay it should be safe, forward the request through to the content - * itself. Only HTML contents currently support executing code - */ - return content_exec(bw->current_content, src, srclen); -} - -/* exported interface documented in browser_window.h */ -nserror browser_window_console_log(struct browser_window *bw, - browser_window_console_source src, - const char *msg, - size_t msglen, - browser_window_console_flags flags) -{ - browser_window_console_flags log_level = flags & BW_CS_FLAG_LEVEL_MASK; - struct browser_window *root = browser_window_get_root(bw); - - assert(msg != NULL); - /* We don't assert msglen > 0, if someone wants to log a real empty - * string then we won't stop them. It does sometimes happen from - * JavaScript for example. - */ - - /* bw is the target of the log, but root is where we log it */ - - NSLOG(netsurf, DEEPDEBUG, "Logging message in %p targetted at %p", root, bw); - NSLOG(netsurf, DEEPDEBUG, "Log came from %s", - ((src == BW_CS_INPUT) ? "user input" : - (src == BW_CS_SCRIPT_ERROR) ? "script error" : - (src == BW_CS_SCRIPT_CONSOLE) ? "script console" : - "unknown input location")); - - switch (log_level) { - case BW_CS_FLAG_LEVEL_DEBUG: - NSLOG(netsurf, DEBUG, "%.*s", (int)msglen, msg); - break; - case BW_CS_FLAG_LEVEL_LOG: - NSLOG(netsurf, VERBOSE, "%.*s", (int)msglen, msg); - break; - case BW_CS_FLAG_LEVEL_INFO: - NSLOG(netsurf, INFO, "%.*s", (int)msglen, msg); - break; - case BW_CS_FLAG_LEVEL_WARN: - NSLOG(netsurf, WARNING, "%.*s", (int)msglen, msg); - break; - case BW_CS_FLAG_LEVEL_ERROR: - NSLOG(netsurf, ERROR, "%.*s", (int)msglen, msg); - break; - default: - /* Unreachable */ - break; - } - - guit->window->console_log(root->window, src, msg, msglen, flags); - - return NSERROR_OK; -} diff --git a/desktop/browser_history.c b/desktop/browser_history.c index 2a5fd63ba..6f807961b 100644 --- a/desktop/browser_history.c +++ b/desktop/browser_history.c @@ -33,13 +33,14 @@ #include "netsurf/layout.h" #include "netsurf/content.h" #include "netsurf/window.h" +#include "netsurf/browser_window.h" #include "content/hlcache.h" #include "content/urldb.h" #include "netsurf/bitmap.h" #include "desktop/gui_internal.h" -#include "desktop/browser_history.h" #include "desktop/browser_private.h" +#include "desktop/browser_history.h" #define WIDTH 100 #define HEIGHT 86 diff --git a/desktop/browser_private.h b/desktop/browser_private.h index 192d22bf0..a5a6dcc9c 100644 --- a/desktop/browser_private.h +++ b/desktop/browser_private.h @@ -22,13 +22,8 @@ * Browser window private structure. */ -#ifndef _NETSURF_DESKTOP_BROWSER_PRIVATE_H_ -#define _NETSURF_DESKTOP_BROWSER_PRIVATE_H_ - -#include - -#include "netsurf/types.h" -#include "netsurf/browser_window.h" +#ifndef NETSURF_DESKTOP_BROWSER_PRIVATE_H_ +#define NETSURF_DESKTOP_BROWSER_PRIVATE_H_ #include "desktop/frame_types.h" diff --git a/desktop/browser_window.c b/desktop/browser_window.c new file mode 100644 index 000000000..c53bd269f --- /dev/null +++ b/desktop/browser_window.c @@ -0,0 +1,3490 @@ +/* + * Copyright 2003 Phil Mellor + * Copyright 2006 James Bursa + * Copyright 2004 Andrew Timmins + * Copyright 2004 John Tytgat + * Copyright 2006 Richard Wilson + * Copyright 2008 Michael Drake + * Copyright 2009 Paul Blokus + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * + * Browser window creation and manipulation implementation. + */ + +#include "utils/config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/corestrings.h" +#include "utils/log.h" +#include "utils/messages.h" +#include "utils/nsurl.h" +#include "utils/utils.h" +#include "utils/utf8.h" +#include "utils/nsoption.h" +#include "netsurf/misc.h" +#include "netsurf/window.h" +#include "netsurf/content.h" +#include "netsurf/plotters.h" +#include "content/content_debug.h" +#include "content/fetch.h" +#include "content/hlcache.h" +#include "content/urldb.h" +#include "css/utils.h" +#include "html/form_internal.h" +#include "html/html.h" +#include "html/box.h" +#include "javascript/js.h" + +#include "desktop/browser_history.h" +#include "desktop/browser_private.h" +#include "desktop/download.h" +#include "desktop/frames.h" +#include "desktop/global_history.h" +#include "desktop/hotlist.h" +#include "desktop/knockout.h" +#include "desktop/scrollbar.h" +#include "desktop/selection.h" +#include "desktop/theme.h" +#include "desktop/gui_internal.h" +#include "desktop/textinput.h" + + +/** maximum frame depth */ +#define FRAME_DEPTH 8 + + +/** + * Get position of scrollbar widget within browser window. + * + * \param bw The browser window + * \param horizontal Whether to get position of horizontal scrollbar + * \param x Updated to x-coord of top left of scrollbar widget + * \param y Updated to y-coord of top left of scrollbar widget + */ +static inline void browser_window_get_scrollbar_pos(struct browser_window *bw, + bool horizontal, int *x, int *y) +{ + if (horizontal) { + *x = 0; + *y = bw->height - SCROLLBAR_WIDTH; + } else { + *x = bw->width - SCROLLBAR_WIDTH; + *y = 0; + } +} + + +/** + * Get browser window scrollbar widget length + * + * \param bw The browser window + * \param horizontal Whether to get length of horizontal scrollbar + * \return the scrollbar's length + */ +static inline int browser_window_get_scrollbar_len(struct browser_window *bw, + bool horizontal) +{ + if (horizontal) + return bw->width - (bw->scroll_y != NULL ? SCROLLBAR_WIDTH : 0); + else + return bw->height; +} + + +/* exported interface, documented in browser.h */ +nserror +browser_window_get_name(struct browser_window *bw, const char **out_name) +{ + assert(bw != NULL); + + *out_name = bw->name; + + return NSERROR_OK; +} + + +/* exported interface, documented in browser.h */ +nserror +browser_window_set_name(struct browser_window *bw, const char *name) +{ + char *nname = NULL; + + assert(bw != NULL); + + if (name != NULL) { + nname = strdup(name); + if (nname == NULL) { + return NSERROR_NOMEM; + } + } + + if (bw->name != NULL) { + free(bw->name); + } + + bw->name = nname; + + return NSERROR_OK; +} + + +/* exported interface, documented in browser.h */ +bool +browser_window_redraw(struct browser_window *bw, + int x, int y, + const struct rect *clip, + const struct redraw_context *ctx) +{ + struct redraw_context new_ctx = *ctx; + int width = 0; + int height = 0; + bool plot_ok = true; + content_type content_type; + struct content_redraw_data data; + struct rect content_clip; + nserror res; + + if (bw == NULL) { + NSLOG(netsurf, INFO, "NULL browser window"); + return false; + } + + if ((bw->current_content == NULL) && + (bw->children == NULL)) { + /* Browser window has no content, render blank fill */ + ctx->plot->clip(ctx, clip); + return (ctx->plot->rectangle(ctx, plot_style_fill_white, clip) == NSERROR_OK); + } + + /* Browser window has content OR children (frames) */ + if ((bw->window != NULL) && + (ctx->plot->option_knockout)) { + /* Root browser window: start knockout */ + knockout_plot_start(ctx, &new_ctx); + } + + new_ctx.plot->clip(ctx, clip); + + /* Handle redraw of any browser window children */ + if (bw->children) { + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + if (bw->window != NULL) { + /* Root browser window; start with blank fill */ + plot_ok &= (new_ctx.plot->rectangle(ctx, + plot_style_fill_white, + clip) == NSERROR_OK); + } + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + child = &bw->children[cur_child]; + + /* Get frame edge box in global coordinates */ + content_clip.x0 = (x + child->x) * child->scale; + content_clip.y0 = (y + child->y) * child->scale; + content_clip.x1 = content_clip.x0 + + child->width * child->scale; + content_clip.y1 = content_clip.y0 + + child->height * child->scale; + + /* Intersect it with clip rectangle */ + if (content_clip.x0 < clip->x0) + content_clip.x0 = clip->x0; + if (content_clip.y0 < clip->y0) + content_clip.y0 = clip->y0; + if (clip->x1 < content_clip.x1) + content_clip.x1 = clip->x1; + if (clip->y1 < content_clip.y1) + content_clip.y1 = clip->y1; + + /* Skip this frame if it lies outside clip rectangle */ + if (content_clip.x0 >= content_clip.x1 || + content_clip.y0 >= content_clip.y1) + continue; + + /* Redraw frame */ + plot_ok &= browser_window_redraw(child, + x + child->x, y + child->y, + &content_clip, &new_ctx); + } + + /* Nothing else to redraw for browser windows with children; + * cleanup and return + */ + if (bw->window != NULL && ctx->plot->option_knockout) { + /* Root browser window: knockout end */ + knockout_plot_end(ctx); + } + + return plot_ok; + } + + /* Handle browser windows with content to redraw */ + + content_type = content_get_type(bw->current_content); + if (content_type != CONTENT_HTML && content_type != CONTENT_TEXTPLAIN) { + /* Set render area according to scale */ + width = content_get_width(bw->current_content) * bw->scale; + height = content_get_height(bw->current_content) * bw->scale; + + /* Non-HTML may not fill viewport to extents, so plot white + * background fill */ + plot_ok &= (new_ctx.plot->rectangle(&new_ctx, + plot_style_fill_white, + clip) == NSERROR_OK); + } + + /* Set up content redraw data */ + data.x = x - scrollbar_get_offset(bw->scroll_x); + data.y = y - scrollbar_get_offset(bw->scroll_y); + data.width = width; + data.height = height; + + data.background_colour = 0xFFFFFF; + data.scale = bw->scale; + data.repeat_x = false; + data.repeat_y = false; + + content_clip = *clip; + + if (!bw->window) { + int x0 = x * bw->scale; + int y0 = y * bw->scale; + int x1 = (x + bw->width - ((bw->scroll_y != NULL) ? + SCROLLBAR_WIDTH : 0)) * bw->scale; + int y1 = (y + bw->height - ((bw->scroll_x != NULL) ? + SCROLLBAR_WIDTH : 0)) * bw->scale; + + if (content_clip.x0 < x0) content_clip.x0 = x0; + if (content_clip.y0 < y0) content_clip.y0 = y0; + if (x1 < content_clip.x1) content_clip.x1 = x1; + if (y1 < content_clip.y1) content_clip.y1 = y1; + } + + /* Render the content */ + plot_ok &= content_redraw(bw->current_content, &data, + &content_clip, &new_ctx); + + /* Back to full clip rect */ + new_ctx.plot->clip(&new_ctx, clip); + + if (!bw->window) { + /* Render scrollbars */ + int off_x, off_y; + if (bw->scroll_x != NULL) { + browser_window_get_scrollbar_pos(bw, true, + &off_x, &off_y); + res = scrollbar_redraw(bw->scroll_x, + x + off_x, y + off_y, clip, + bw->scale, &new_ctx); + if (res != NSERROR_OK) { + plot_ok = false; + } + } + if (bw->scroll_y != NULL) { + browser_window_get_scrollbar_pos(bw, false, + &off_x, &off_y); + res = scrollbar_redraw(bw->scroll_y, + x + off_x, y + off_y, clip, + bw->scale, &new_ctx); + if (res != NSERROR_OK) { + plot_ok = false; + } + } + } + + if (bw->window != NULL && ctx->plot->option_knockout) { + /* Root browser window: end knockout */ + knockout_plot_end(ctx); + } + + return plot_ok; +} + + +/* exported interface, documented in browser.h */ +bool browser_window_redraw_ready(struct browser_window *bw) +{ + if (bw == NULL) { + NSLOG(netsurf, INFO, "NULL browser window"); + return false; + } else if (bw->current_content != NULL) { + /* Can't render locked contents */ + return !content_is_locked(bw->current_content); + } + + return true; +} + + +/* exported interface, documented in browser_private.h */ +void browser_window_update_extent(struct browser_window *bw) +{ + if (bw->window != NULL) + /* Front end window */ + guit->window->update_extent(bw->window); + else + /* Core-managed browser window */ + browser_window_handle_scrollbars(bw); +} + + +/* exported interface, documented in browser.h */ +void +browser_window_get_position(struct browser_window *bw, + bool root, + int *pos_x, + int *pos_y) +{ + *pos_x = 0; + *pos_y = 0; + + assert(bw != NULL); + + while (bw) { + switch (bw->browser_window_type) { + + case BROWSER_WINDOW_FRAMESET: + *pos_x += bw->x * bw->scale; + *pos_y += bw->y * bw->scale; + break; + + case BROWSER_WINDOW_NORMAL: + /* There is no offset to the root browser window */ + break; + + case BROWSER_WINDOW_FRAME: + /* Iframe and Frame handling is identical; + * fall though */ + case BROWSER_WINDOW_IFRAME: + *pos_x += (bw->x - scrollbar_get_offset(bw->scroll_x)) * + bw->scale; + *pos_y += (bw->y - scrollbar_get_offset(bw->scroll_y)) * + bw->scale; + break; + } + + bw = bw->parent; + + if (!root) { + /* return if we just wanted the position in the parent + * browser window. */ + return; + } + } +} + + +/* exported interface, documented in browser.h */ +void browser_window_set_position(struct browser_window *bw, int x, int y) +{ + assert(bw != NULL); + + if (bw->window == NULL) { + /* Core managed browser window */ + bw->x = x; + bw->y = y; + } else { + NSLOG(netsurf, INFO, + "Asked to set position of front end window."); + assert(0); + } +} + +/* exported interface, documented in browser.h */ +void +browser_window_set_drag_type(struct browser_window *bw, + browser_drag_type type, + const struct rect *rect) +{ + struct browser_window *top_bw = browser_window_get_root(bw); + gui_drag_type gtype; + + bw->drag.type = type; + + if (type == DRAGGING_NONE) { + top_bw->drag.window = NULL; + } else { + top_bw->drag.window = bw; + + switch (type) { + case DRAGGING_SELECTION: + /** \todo tell front end */ + return; + case DRAGGING_SCR_X: + case DRAGGING_SCR_Y: + case DRAGGING_CONTENT_SCROLLBAR: + gtype = GDRAGGING_SCROLLBAR; + break; + default: + gtype = GDRAGGING_OTHER; + break; + } + + guit->window->drag_start(top_bw->window, gtype, rect); + } +} + +/* exported interface, documented in browser.h */ +browser_drag_type browser_window_get_drag_type(struct browser_window *bw) +{ + return bw->drag.type; +} + +/* exported interface, documented in browser.h */ +struct browser_window * browser_window_get_root(struct browser_window *bw) +{ + while (bw && bw->parent) { + bw = bw->parent; + } + return bw; +} + +/* exported interface, documented in browser.h */ +browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw) +{ + browser_editor_flags ed_flags = BW_EDITOR_NONE; + assert(bw->window); + assert(bw->parent == NULL); + + if (bw->selection.bw != NULL) { + ed_flags |= BW_EDITOR_CAN_COPY; + + if (!bw->selection.read_only) + ed_flags |= BW_EDITOR_CAN_CUT; + } + + if (bw->can_edit) + ed_flags |= BW_EDITOR_CAN_PASTE; + + return ed_flags; +} + +/* exported interface, documented in browser.h */ +bool browser_window_can_select(struct browser_window *bw) +{ + if (bw == NULL || bw->current_content == NULL) + return false; + + /* TODO: We shouldn't have to know about specific content types + * here. There should be a content_is_selectable() call. */ + if (content_get_type(bw->current_content) != CONTENT_HTML && + content_get_type(bw->current_content) != + CONTENT_TEXTPLAIN) + return false; + + return true; +} + +/* exported interface, documented in browser.h */ +char * browser_window_get_selection(struct browser_window *bw) +{ + assert(bw->window); + assert(bw->parent == NULL); + + if (bw->selection.bw == NULL || + bw->selection.bw->current_content == NULL) + return NULL; + + return content_get_selection(bw->selection.bw->current_content); +} + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_can_search(struct browser_window *bw) +{ + if (bw == NULL || bw->current_content == NULL) + return false; + + /** \todo We shouldn't have to know about specific content + * types here. There should be a content_is_searchable() call. + */ + if ((content_get_type(bw->current_content) != CONTENT_HTML) && + (content_get_type(bw->current_content) != CONTENT_TEXTPLAIN)) { + return false; + } + + return true; +} + + +/* exported interface, documented in netsurf/browser_window.h */ +bool browser_window_is_frameset(struct browser_window *bw) +{ + return (bw->children != NULL); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_get_scrollbar_type(struct browser_window *bw, + browser_scrolling *h, browser_scrolling *v) +{ + *h = bw->scrolling; + *v = bw->scrolling; + + return NSERROR_OK; +} + +/** + * Set or remove a selection. + * + * \param bw browser window with selection + * \param selection true if bw has a selection, false if removing selection + * \param read_only true iff selection is read only (e.g. can't cut it) + */ +static void browser_window_set_selection(struct browser_window *bw, + bool selection, bool read_only) +{ + struct browser_window *top; + + assert(bw != NULL); + + top = browser_window_get_root(bw); + + assert(top != NULL); + + if (bw != top->selection.bw && top->selection.bw != NULL && + top->selection.bw->current_content != NULL) { + /* clear old selection */ + content_clear_selection(top->selection.bw->current_content); + } + + if (selection) { + top->selection.bw = bw; + } else { + top->selection.bw = NULL; + } + + top->selection.read_only = read_only; +} + + +/** + * Set the scroll position of a browser window. + * + * scrolls the viewport to ensure the specified rectangle of the + * content is shown. + * + * \param bw window to scroll + * \param rect The rectangle to ensure is shown. + * \return NSERROR_OK on success or apropriate error code. + */ +static nserror +browser_window_set_scroll(struct browser_window *bw, + const struct rect *rect) +{ + if (bw->window != NULL) { + return guit->window->set_scroll(bw->window, rect); + } + + if (bw->scroll_x != NULL) { + scrollbar_set(bw->scroll_x, rect->x0, false); + } + if (bw->scroll_y != NULL) { + scrollbar_set(bw->scroll_y, rect->y0, false); + } + + return NSERROR_OK; +} + +/** + * Internal helper for getting the positional features + * + * \param[in] bw browser window to examine. + * \param[in] x x-coordinate of point of interest + * \param[in] y y-coordinate of point of interest + * \param[out] data Feature structure to update. + * \return NSERROR_OK or appropriate error code on faliure. + */ +static nserror +browser_window__get_contextual_content(struct browser_window *bw, + int x, int y, struct browser_window_features *data) +{ + nserror ret = NSERROR_OK; + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child. + */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if ((x < bwc->x) || + (bwc->x + bwc->width < x) || + (y < bwc->y) || + (bwc->y + bwc->height < y)) { + continue; + } + + /* Pass request into this child */ + return browser_window__get_contextual_content(bwc, + (x - bwc->x), (y - bwc->y), data); + } + + /* Coordinate not contained by any frame */ + + } else if (bw->current_content != NULL) { + /* Pass request to content */ + ret = content_get_contextual_content(bw->current_content, + x, y, data); + data->main = bw->current_content; + } + + return ret; +} + +/* exported interface, documented in browser.h */ +nserror browser_window_get_features(struct browser_window *bw, + int x, int y, struct browser_window_features *data) +{ + /* clear the features structure to empty values */ + data->link = NULL; + data->object = NULL; + data->main = NULL; + data->form_features = CTX_FORM_NONE; + + return browser_window__get_contextual_content(bw, x, y, data); +} + +/* exported interface, documented in browser.h */ +bool browser_window_scroll_at_point(struct browser_window *bw, + int x, int y, int scrx, int scry) +{ + bool handled_scroll = false; + assert(bw != NULL); + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if (x < bwc->x || bwc->x + bwc->width < x || + y < bwc->y || bwc->y + bwc->height < y) + continue; + + /* Pass request into this child */ + return browser_window_scroll_at_point(bwc, + (x - bwc->x), (y - bwc->y), + scrx, scry); + } + } + + /* Try to scroll any current content */ + if (bw->current_content != NULL && content_scroll_at_point( + bw->current_content, x, y, scrx, scry) == true) + /* Scroll handled by current content */ + return true; + + /* Try to scroll this window, if scroll not already handled */ + if (handled_scroll == false) { + if (bw->scroll_y && scrollbar_scroll(bw->scroll_y, scry)) + handled_scroll = true; + + if (bw->scroll_x && scrollbar_scroll(bw->scroll_x, scrx)) + handled_scroll = true; + } + + return handled_scroll; +} + +/* exported interface, documented in browser.h */ +bool browser_window_drop_file_at_point(struct browser_window *bw, + int x, int y, char *file) +{ + assert(bw != NULL); + + /* Handle (i)frame scroll offset (core-managed browser windows only) */ + x += scrollbar_get_offset(bw->scroll_x); + y += scrollbar_get_offset(bw->scroll_y); + + if (bw->children) { + /* Browser window has children, so pass request on to + * appropriate child */ + struct browser_window *bwc; + int cur_child; + int children = bw->rows * bw->cols; + + /* Loop through all children of bw */ + for (cur_child = 0; cur_child < children; cur_child++) { + /* Set current child */ + bwc = &bw->children[cur_child]; + + /* Skip this frame if (x, y) coord lies outside */ + if (x < bwc->x || bwc->x + bwc->width < x || + y < bwc->y || bwc->y + bwc->height < y) + continue; + + /* Pass request into this child */ + return browser_window_drop_file_at_point(bwc, + (x - bwc->x), (y - bwc->y), + file); + } + } + + /* Pass file drop on to any content */ + if (bw->current_content != NULL) + return content_drop_file_at_point(bw->current_content, + x, y, file); + + return false; +} + +/* exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_gadget_filename(struct browser_window *bw, + struct form_control *gadget, const char *fn) +{ + html_set_file_gadget_filename(bw->current_content, gadget, fn); +} + +/* exported interface, documented in browser.h */ +nserror browser_window_debug_dump(struct browser_window *bw, + FILE *f, enum content_debug op) +{ + if (bw->current_content != NULL) { + return content_debug_dump(bw->current_content, f, op); + } + return NSERROR_OK; +} + +/* exported interface, documented in browser.h */ +nserror browser_window_debug(struct browser_window *bw, enum content_debug op) +{ + if (bw->current_content != NULL) { + return content_debug(bw->current_content, op); + } + return NSERROR_OK; +} + +/** slow script handler +*/ +static bool slow_script(void *ctx) +{ + static int count = 0; + NSLOG(netsurf, INFO, "Continuing execution %d", count); + count++; + if (count > 1) { + count = 0; + return false; + } + return true; +} + +/* exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_create(enum browser_window_create_flags flags, + nsurl *url, nsurl *referrer, + struct browser_window *existing, + struct browser_window **bw) +{ + gui_window_create_flags gw_flags = GW_CREATE_NONE; + struct browser_window *ret; + nserror err; + + /* Check parameters */ + if (flags & BW_CREATE_CLONE) { + if (existing == NULL) { + assert(0 && "Failed: No existing window provided."); + return NSERROR_BAD_PARAMETER; + } + } + if (!(flags & BW_CREATE_HISTORY)) { + if (!(flags & BW_CREATE_CLONE) || existing == NULL) { + assert(0 && "Failed: Must have existing for history."); + return NSERROR_BAD_PARAMETER; + } + } + + + if ((ret = calloc(1, sizeof(struct browser_window))) == NULL) { + return NSERROR_NOMEM; + } + + /* Initialise common parts */ + err = browser_window_initialise_common(flags, ret, existing); + if (err != NSERROR_OK) { + browser_window_destroy(ret); + return err; + } + + /* window characteristics */ + ret->browser_window_type = BROWSER_WINDOW_NORMAL; + ret->scrolling = BW_SCROLLING_YES; + ret->border = true; + ret->no_resize = true; + ret->focus = ret; + + /* initialise last action with creation time */ + nsu_getmonotonic_ms(&ret->last_action); + + /* The existing gui_window is on the top-level existing + * browser_window. */ + existing = browser_window_get_root(existing); + + /* Set up gui_window creation flags */ + if (flags & BW_CREATE_TAB) + gw_flags |= GW_CREATE_TAB; + if (flags & BW_CREATE_CLONE) + gw_flags |= GW_CREATE_CLONE; + + ret->window = guit->window->create(ret, + (existing != NULL) ? existing->window : NULL, + gw_flags); + + if (ret->window == NULL) { + browser_window_destroy(ret); + return NSERROR_BAD_PARAMETER; + } + + if (url != NULL) { + enum browser_window_nav_flags nav_flags = BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE; + if (flags & BW_CREATE_UNVERIFIABLE) + nav_flags |= BW_NAVIGATE_UNVERIFIABLE; + if (flags & BW_CREATE_HISTORY) + nav_flags |= BW_NAVIGATE_HISTORY; + browser_window_navigate(ret, url, referrer, nav_flags, NULL, + NULL, NULL); + } + + if (bw != NULL) { + *bw = ret; + } + + return NSERROR_OK; +} + + +/* exported internal interface, documented in desktop/browser_private.h */ +nserror browser_window_initialise_common(enum browser_window_create_flags flags, + struct browser_window *bw, struct browser_window *existing) +{ + nserror err; + assert(bw); + + /* new javascript context for each window/(i)frame */ + err = js_newcontext(nsoption_int(script_timeout), + slow_script, NULL, &bw->jsctx); + if (err != NSERROR_OK) + return err; + + if (flags & BW_CREATE_CLONE) { + assert(existing != NULL); + + /* clone history */ + err = browser_window_history_clone(existing, bw); + + /* copy the scale */ + bw->scale = existing->scale; + } else { + /* create history */ + err = browser_window_history_create(bw); + + /* default scale */ + bw->scale = (float) nsoption_int(scale) / 100.0; + } + + if (err != NSERROR_OK) + return err; + + /* window characteristics */ + bw->refresh_interval = -1; + + bw->drag.type = DRAGGING_NONE; + + bw->scroll_x = NULL; + bw->scroll_y = NULL; + + bw->focus = NULL; + + /* initialise status text cache */ + bw->status.text = NULL; + bw->status.text_len = 0; + bw->status.match = 0; + bw->status.miss = 0; + + return NSERROR_OK; +} + +/** + * implements the download operation of a window navigate + */ +static nserror +browser_window_download(struct browser_window *bw, + nsurl *url, + nsurl *nsref, + uint32_t fetch_flags, + bool fetch_is_post, + llcache_post_data *post) +{ + llcache_handle *l; + struct browser_window *root; + nserror error; + + root = browser_window_get_root(bw); + assert(root != NULL); + + fetch_flags |= LLCACHE_RETRIEVE_FORCE_FETCH; + fetch_flags |= LLCACHE_RETRIEVE_STREAM_DATA; + + error = llcache_handle_retrieve(url, fetch_flags, nsref, + fetch_is_post ? post : NULL, + NULL, NULL, &l); + if (error == NSERROR_NO_FETCH_HANDLER) { + /* no internal handler for this type, call out to frontend */ + error = guit->misc->launch_url(url); + } else if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, "Failed to fetch download: %d", error); + } else { + error = download_context_create(l, root->window); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Failed creating download context: %d", error); + llcache_handle_abort(l); + llcache_handle_release(l); + } + } + + return error; +} + + +static bool browser_window_check_throbber(struct browser_window *bw) +{ + int children, index; + + if (bw->throbbing) + return true; + + if (bw->children) { + children = bw->rows * bw->cols; + for (index = 0; index < children; index++) { + if (browser_window_check_throbber(&bw->children[index])) + return true; + } + } + if (bw->iframes) { + for (index = 0; index < bw->iframe_count; index++) { + if (browser_window_check_throbber(&bw->iframes[index])) + return true; + } + } + return false; +} + + +/** + * Start the busy indicator. + * + * \param bw browser window + */ + +static void browser_window_start_throbber(struct browser_window *bw) +{ + bw->throbbing = true; + + while (bw->parent) + bw = bw->parent; + + guit->window->start_throbber(bw->window); +} + + +/** + * Stop the busy indicator. + * + * \param bw browser window + */ +static void browser_window_stop_throbber(struct browser_window *bw) +{ + bw->throbbing = false; + + while (bw->parent) + bw = bw->parent; + + if (!browser_window_check_throbber(bw)) { + guit->window->stop_throbber(bw->window); + } +} + + + +/** + * Callback for fetchcache() for browser window favicon fetches. + * + * \param c content handle of favicon + * \param event The event to process + * \param pw a context containing the browser window + * \return NSERROR_OK on success else appropriate error code. + */ +static nserror +browser_window_favicon_callback(hlcache_handle *c, + const hlcache_event *event, + void *pw) +{ + struct browser_window *bw = pw; + + switch (event->type) { + case CONTENT_MSG_DONE: + if (bw->favicon.current != NULL) { + content_close(bw->favicon.current); + hlcache_handle_release(bw->favicon.current); + } + + bw->favicon.current = c; + bw->favicon.loading = NULL; + + /* content_get_bitmap on the hlcache_handle should give + * the favicon bitmap at this point + */ + guit->window->set_icon(bw->window, c); + break; + + case CONTENT_MSG_ERROR: + case CONTENT_MSG_ERRORCODE: + + /* clean up after ourselves */ + if (c == bw->favicon.loading) { + bw->favicon.loading = NULL; + } else if (c == bw->favicon.current) { + bw->favicon.current = NULL; + } + + hlcache_handle_release(c); + + if (bw->favicon.failed == false) { + nsurl *nsref = NULL; + nsurl *nsurl; + nserror error; + + bw->favicon.failed = true; + + error = nsurl_create("resource:favicon.ico", &nsurl); + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Unable to create default location url"); + } else { + hlcache_handle_retrieve(nsurl, + HLCACHE_RETRIEVE_SNIFF_TYPE, + nsref, NULL, + browser_window_favicon_callback, + bw, NULL, CONTENT_IMAGE, + &bw->favicon.loading); + + nsurl_unref(nsurl); + } + + } + break; + + default: + break; + } + return NSERROR_OK; +} + + +/** + * update the favicon associated with the browser window + * + * \param c the page content handle. + * \param bw A top level browser window. + * \param link A link context or NULL to attempt fallback scanning. + */ +static void +browser_window_update_favicon(hlcache_handle *c, + struct browser_window *bw, + struct content_rfc5988_link *link) +{ + nsurl *nsref = NULL; + nsurl *nsurl; + nserror error; + + assert(c != NULL); + assert(bw !=NULL); + + if (bw->window == NULL) + /* Not top-level browser window; not interested */ + return; + + /* already fetching the favicon - use that */ + if (bw->favicon.loading != NULL) + return; + + bw->favicon.failed = false; + + if (link == NULL) { + /* Look for "icon" */ + link = content_find_rfc5988_link(c, corestring_lwc_icon); + } + + if (link == NULL) { + /* Look for "shortcut icon" */ + link = content_find_rfc5988_link(c, + corestring_lwc_shortcut_icon); + } + + if (link == NULL) { + lwc_string *scheme; + bool speculative_default = false; + bool match; + + nsurl = hlcache_handle_get_url(c); + + scheme = nsurl_get_component(nsurl, NSURL_SCHEME); + + /* If the document was fetched over http(s), then speculate + * that there's a favicon living at /favicon.ico */ + if ((lwc_string_caseless_isequal(scheme, corestring_lwc_http, + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal(scheme, corestring_lwc_https, + &match) == lwc_error_ok && match)) { + speculative_default = true; + } + + lwc_string_unref(scheme); + + if (speculative_default) { + /* no favicon via link, try for the default location */ + error = nsurl_join(nsurl, "/favicon.ico", &nsurl); + } else { + bw->favicon.failed = true; + error = nsurl_create("resource:favicon.ico", &nsurl); + } + if (error != NSERROR_OK) { + NSLOG(netsurf, INFO, + "Unable to create default location url"); + return; + } + } else { + nsurl = nsurl_ref(link->href); + } + + if (link == NULL) { + NSLOG(netsurf, INFO, "fetching general favicon from '%s'", + nsurl_access(nsurl)); + } else { + NSLOG(netsurf, INFO, "fetching favicon rel:%s '%s'", + lwc_string_data(link->rel), nsurl_access(nsurl)); + } + + hlcache_handle_retrieve(nsurl, HLCACHE_RETRIEVE_SNIFF_TYPE, + nsref, NULL, browser_window_favicon_callback, + bw, NULL, CONTENT_IMAGE, &bw->favicon.loading); + + nsurl_unref(nsurl); +} + +/** + * window callback errorcode handling. + */ +static void +browser_window_callback_errorcode(hlcache_handle *c, + struct browser_window *bw, + nserror code) +{ + const char* message; + + message = messages_get_errorcode(code); + + browser_window_set_status(bw, message); + + /* Only warn the user about errors in top-level windows */ + if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { + guit->misc->warning(message, NULL); + } + + if (c == bw->loading_content) { + bw->loading_content = NULL; + } else if (c == bw->current_content) { + bw->current_content = NULL; + browser_window_remove_caret(bw, false); + } + + hlcache_handle_release(c); + + browser_window_stop_throbber(bw); +} + + +/** + * Handle meta http-equiv refresh time elapsing by loading a new page. + * + * \param p browser window to refresh with new page + */ +static void browser_window_refresh(void *p) +{ + struct browser_window *bw = p; + nsurl *url; + nsurl *refresh; + hlcache_handle *parent = NULL; + enum browser_window_nav_flags flags = BW_NAVIGATE_UNVERIFIABLE; + + assert(bw->current_content != NULL && + (content_get_status(bw->current_content) == + CONTENT_STATUS_READY || + content_get_status(bw->current_content) == + CONTENT_STATUS_DONE)); + + /* Ignore if the refresh URL has gone + * (may happen if a fetch error occurred) */ + refresh = content_get_refresh_url(bw->current_content); + if (refresh == NULL) + return; + + /* mark this content as invalid so it gets flushed from the cache */ + content_invalidate_reuse_data(bw->current_content); + + url = hlcache_handle_get_url(bw->current_content); + if ((url == NULL) || (nsurl_compare(url, refresh, NSURL_COMPLETE))) { + flags |= BW_NAVIGATE_HISTORY; + } + + /* Treat an (almost) immediate refresh in a top-level browser window as + * if it were an HTTP redirect, and thus make the resulting fetch + * verifiable. + * + * See fetchcache.c for why redirected fetches should be verifiable at + * all. + */ + if (bw->refresh_interval <= 100 && bw->parent == NULL) { + flags &= ~BW_NAVIGATE_UNVERIFIABLE; + } else { + parent = bw->current_content; + } + + browser_window_navigate(bw, + refresh, + url, + flags, + NULL, + NULL, + parent); + +} + + +/** + * Transfer the loading_content to a new download window. + */ + +static void browser_window_convert_to_download(struct browser_window *bw, + llcache_handle *stream) +{ + struct browser_window *root = browser_window_get_root(bw); + nserror error; + + assert(root != NULL); + + error = download_context_create(stream, root->window); + if (error != NSERROR_OK) { + llcache_handle_abort(stream); + llcache_handle_release(stream); + } + + /* remove content from browser window */ + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + + browser_window_stop_throbber(bw); +} + +/** + * handle message for content ready on browser window + */ +static nserror +browser_window_content_ready(struct browser_window *bw) +{ + int width, height; + nserror res = NSERROR_OK; + + /* close and release the current window content */ + if (bw->current_content != NULL) { + content_close(bw->current_content); + hlcache_handle_release(bw->current_content); + } + + bw->current_content = bw->loading_content; + bw->loading_content = NULL; + + /* Format the new content to the correct dimensions */ + browser_window_get_dimensions(bw, &width, &height, true); + content_reformat(bw->current_content, false, width, height); + + /* history */ + if (bw->history_add && bw->history) { + nsurl *url = hlcache_handle_get_url(bw->current_content); + + if (urldb_add_url(url)) { + urldb_set_url_title(url, content_get_title(bw->current_content)); + urldb_update_url_visit_data(url); + urldb_set_url_content_type(url, + content_get_type(bw->current_content)); + + /* This is safe as we've just added the URL */ + global_history_add(urldb_get_url(url)); + } + /** + * \todo Urldb / Thumbnails / Local history brokenness + * + * We add to local history after calling urldb_add_url rather + * than in the block above. If urldb_add_url fails (as it + * will for urls like "about:about", "about:config" etc), + * there would be no local history node, and later calls to + * history_update will either explode or overwrite the node + * for the previous URL. + * + * We call it after, rather than before urldb_add_url because + * history_add calls bitmap render, which tries to register + * the thumbnail with urldb. That thumbnail registration + * fails if the url doesn't exist in urldb already, and only + * urldb-registered thumbnails get freed. So if we called + * history_add before urldb_add_url we would leak thumbnails + * for all newly visited URLs. With the history_add call + * after, we only leak the thumbnails when urldb does not add + * the URL. + * + * Also, since browser_window_history_add can create a + * thumbnail (content_redraw), we need to do it after + * content_reformat. + */ + browser_window_history_add(bw, bw->current_content, bw->frag_id); + } + + browser_window_remove_caret(bw, false); + + if (bw->window != NULL) { + guit->window->new_content(bw->window); + + browser_window_refresh_url_bar(bw); + } + + /* new content; set scroll_to_top */ + browser_window_update(bw, true); + content_open(bw->current_content, bw, 0, 0); + browser_window_set_status(bw, content_get_status_message(bw->current_content)); + + /* frames */ + if ((content_get_type(bw->current_content) == CONTENT_HTML) && + (html_get_frameset(bw->current_content) != NULL)) { + res = browser_window_create_frameset(bw, html_get_frameset(bw->current_content)); + } + + if (content_get_type(bw->current_content) == CONTENT_HTML && + html_get_iframe(bw->current_content) != NULL) { + browser_window_create_iframes(bw, html_get_iframe(bw->current_content)); + } + + return res; +} + + +/** + * handle message for content done on browser window + */ +static nserror +browser_window_content_done(struct browser_window *bw) +{ + float sx, sy; + struct rect rect; + int scrollx; + int scrolly; + + if (bw->window == NULL) { + /* Updated browser window's scrollbars. */ + /** + * \todo update browser window scrollbars before CONTENT_MSG_DONE + */ + browser_window_reformat(bw, true, bw->width, bw->height); + browser_window_handle_scrollbars(bw); + } + + browser_window_update(bw, false); + browser_window_set_status(bw, content_get_status_message(bw->current_content)); + browser_window_stop_throbber(bw); + browser_window_update_favicon(bw->current_content, bw, NULL); + + if (browser_window_history_get_scroll(bw, &sx, &sy) == NSERROR_OK) { + scrollx = (int)((float)content_get_width(bw->current_content) * sx); + scrolly = (int)((float)content_get_height(bw->current_content) * sy); + rect.x0 = rect.x1 = scrollx; + rect.y0 = rect.y1 = scrolly; + if (browser_window_set_scroll(bw, &rect) != NSERROR_OK) { + NSLOG(netsurf, WARNING, + "Unable to set browser scroll offsets to %d by %d", + scrollx, scrolly); + } + } + + browser_window_history_update(bw, bw->current_content); + hotlist_update_url(hlcache_handle_get_url(bw->current_content)); + + if (bw->refresh_interval != -1) { + guit->misc->schedule(bw->refresh_interval * 10, + browser_window_refresh, bw); + } + + return NSERROR_OK; +} + +/** + * Browser window content event callback handler. + */ +static nserror +browser_window_callback(hlcache_handle *c, + const hlcache_event *event, + void *pw) +{ + struct browser_window *bw = pw; + nserror res = NSERROR_OK; + + switch (event->type) { + case CONTENT_MSG_LOG: + browser_window_console_log(bw, + event->data.log.src, + event->data.log.msg, + event->data.log.msglen, + event->data.log.flags); + break; + case CONTENT_MSG_DOWNLOAD: + assert(bw->loading_content == c); + + browser_window_convert_to_download(bw, event->data.download); + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + break; + + case CONTENT_MSG_LOADING: + assert(bw->loading_content == c); + +#ifdef WITH_THEME_INSTALL + if (content_get_type(c) == CONTENT_THEME) { + theme_install_start(c); + bw->loading_content = NULL; + browser_window_stop_throbber(bw); + } else +#endif + { + bw->refresh_interval = -1; + browser_window_set_status(bw, + content_get_status_message(c)); + } + break; + + case CONTENT_MSG_READY: + assert(bw->loading_content == c); + + res = browser_window_content_ready(bw); + break; + + case CONTENT_MSG_DONE: + assert(bw->current_content == c); + + res = browser_window_content_done(bw); + break; + + case CONTENT_MSG_ERRORCODE: + browser_window_callback_errorcode(c, bw, event->data.errorcode); + break; + + case CONTENT_MSG_ERROR: + browser_window_set_status(bw, event->data.error); + + /* Only warn the user about errors in top-level windows */ + if (bw->browser_window_type == BROWSER_WINDOW_NORMAL) { + guit->misc->warning(event->data.error, NULL); + } + + if (c == bw->loading_content) { + bw->loading_content = NULL; + } else if (c == bw->current_content) { + bw->current_content = NULL; + browser_window_remove_caret(bw, false); + } + + hlcache_handle_release(c); + + browser_window_stop_throbber(bw); + break; + + case CONTENT_MSG_REDIRECT: + if (urldb_add_url(event->data.redirect.from)) + urldb_update_url_visit_data(event->data.redirect.from); + break; + + case CONTENT_MSG_STATUS: + if (event->data.explicit_status_text == NULL) { + /* Object content's status text updated */ + const char *status = NULL; + if (bw->loading_content != NULL) + /* Give preference to any loading content */ + status = content_get_status_message( + bw->loading_content); + + if (status == NULL) + status = content_get_status_message(c); + + if (status != NULL) + browser_window_set_status(bw, status); + } else { + /* Object content wants to set explicit message */ + browser_window_set_status(bw, + event->data.explicit_status_text); + } + break; + + case CONTENT_MSG_REFORMAT: + if (c == bw->current_content && + content_get_type(c) == CONTENT_HTML) { + /* reposition frames */ + if (html_get_frameset(c) != NULL) + browser_window_recalculate_frameset(bw); + /* reflow iframe positions */ + if (html_get_iframe(c) != NULL) + browser_window_recalculate_iframes(bw); + } + + /* Hide any caret, but don't remove it */ + browser_window_remove_caret(bw, true); + + if (!(event->data.background)) { + /* Reformatted content should be redrawn */ + browser_window_update(bw, false); + } + break; + + case CONTENT_MSG_REDRAW: + { + struct rect rect = { + .x0 = event->data.redraw.x, + .y0 = event->data.redraw.y, + .x1 = event->data.redraw.x + event->data.redraw.width, + .y1 = event->data.redraw.y + event->data.redraw.height + }; + + browser_window_update_box(bw, &rect); + } + break; + + case CONTENT_MSG_REFRESH: + bw->refresh_interval = event->data.delay * 100; + break; + + case CONTENT_MSG_LINK: /* content has an rfc5988 link element */ + { + bool match; + + /* Handle "icon" and "shortcut icon" */ + if ((lwc_string_caseless_isequal( + event->data.rfc5988_link->rel, + corestring_lwc_icon, + &match) == lwc_error_ok && match) || + (lwc_string_caseless_isequal( + event->data.rfc5988_link->rel, + corestring_lwc_shortcut_icon, + &match) == lwc_error_ok && match)) { + /* it's a favicon perhaps start a fetch for it */ + browser_window_update_favicon(c, bw, + event->data.rfc5988_link); + } + } + break; + + case CONTENT_MSG_GETCTX: + /* only the content object created by the browser + * window requires a new global compartment object + */ + assert(bw->loading_content == c); + if (js_newcompartment(bw->jsctx, + bw, + hlcache_handle_get_content(c)) != NULL) { + *(event->data.jscontext) = bw->jsctx; + } + break; + + case CONTENT_MSG_GETDIMS: + { + int width; + int height; + + browser_window_get_dimensions(bw, &width, &height, true); + + *(event->data.getdims.viewport_width) = width; + *(event->data.getdims.viewport_height) = height; + break; + } + + case CONTENT_MSG_SCROLL: + { + struct rect rect = { + .x0 = event->data.scroll.x0, + .y0 = event->data.scroll.y0, + }; + + /* Content wants to be scrolled */ + if (bw->current_content != c) { + break; + } + + if (event->data.scroll.area) { + rect.x1 = event->data.scroll.x1; + rect.y1 = event->data.scroll.y1; + } else { + rect.x1 = event->data.scroll.x0; + rect.y1 = event->data.scroll.y0; + } + browser_window_set_scroll(bw, &rect); + + break; + } + + case CONTENT_MSG_DRAGSAVE: + { + /* Content wants drag save of a content */ + struct browser_window *root = browser_window_get_root(bw); + hlcache_handle *save = event->data.dragsave.content; + + if (save == NULL) { + save = c; + } + + switch(event->data.dragsave.type) { + case CONTENT_SAVE_ORIG: + guit->window->drag_save_object(root->window, save, + GUI_SAVE_OBJECT_ORIG); + break; + + case CONTENT_SAVE_NATIVE: + guit->window->drag_save_object(root->window, save, + GUI_SAVE_OBJECT_NATIVE); + break; + + case CONTENT_SAVE_COMPLETE: + guit->window->drag_save_object(root->window, save, + GUI_SAVE_COMPLETE); + break; + + case CONTENT_SAVE_SOURCE: + guit->window->drag_save_object(root->window, save, + GUI_SAVE_SOURCE); + break; + } + } + break; + + case CONTENT_MSG_SAVELINK: + { + /* Content wants a link to be saved */ + struct browser_window *root = browser_window_get_root(bw); + guit->window->save_link(root->window, + event->data.savelink.url, + event->data.savelink.title); + } + break; + + case CONTENT_MSG_POINTER: + /* Content wants to have specific mouse pointer */ + browser_window_set_pointer(bw, event->data.pointer); + break; + + case CONTENT_MSG_DRAG: + { + browser_drag_type bdt = DRAGGING_NONE; + + switch (event->data.drag.type) { + case CONTENT_DRAG_NONE: + bdt = DRAGGING_NONE; + break; + case CONTENT_DRAG_SCROLL: + bdt = DRAGGING_CONTENT_SCROLLBAR; + break; + case CONTENT_DRAG_SELECTION: + bdt = DRAGGING_SELECTION; + break; + } + browser_window_set_drag_type(bw, bdt, event->data.drag.rect); + } + break; + + case CONTENT_MSG_CARET: + switch (event->data.caret.type) { + case CONTENT_CARET_REMOVE: + browser_window_remove_caret(bw, false); + break; + case CONTENT_CARET_HIDE: + browser_window_remove_caret(bw, true); + break; + case CONTENT_CARET_SET_POS: + browser_window_place_caret(bw, + event->data.caret.pos.x, + event->data.caret.pos.y, + event->data.caret.pos.height, + event->data.caret.pos.clip); + break; + } + break; + + case CONTENT_MSG_SELECTION: + browser_window_set_selection(bw, + event->data.selection.selection, + event->data.selection.read_only); + break; + + case CONTENT_MSG_SELECTMENU: + if (event->data.select_menu.gadget->type == GADGET_SELECT) { + struct browser_window *root = + browser_window_get_root(bw); + guit->window->create_form_select_menu(root->window, + event->data.select_menu.gadget); + } + + break; + + case CONTENT_MSG_GADGETCLICK: + if (event->data.gadget_click.gadget->type == GADGET_FILE) { + struct browser_window *root = + browser_window_get_root(bw); + guit->window->file_gadget_open(root->window, c, + event->data.gadget_click.gadget); + } + + break; + + default: + break; + } + + return res; +} + + +/* Have to forward declare browser_window_destroy_internal */ +static void browser_window_destroy_internal(struct browser_window *bw); + +/** + * Close and destroy all child browser window. + * + * \param bw browser window + */ +static void browser_window_destroy_children(struct browser_window *bw) +{ + int i; + + if (bw->children) { + for (i = 0; i < (bw->rows * bw->cols); i++) + browser_window_destroy_internal(&bw->children[i]); + free(bw->children); + bw->children = NULL; + bw->rows = 0; + bw->cols = 0; + } + if (bw->iframes) { + for (i = 0; i < bw->iframe_count; i++) + browser_window_destroy_internal(&bw->iframes[i]); + free(bw->iframes); + bw->iframes = NULL; + bw->iframe_count = 0; + } +} + + +/** + * internal scheduled reformat callback. + * + * scheduled reformat callback to allow reformats from unthreaded context. + * + * \param vbw The browser window to be reformatted + */ +static void scheduled_reformat(void *vbw) +{ + struct browser_window *bw = vbw; + int width; + int height; + nserror res; + + res = guit->window->get_dimensions(bw->window, &width, &height, false); + if (res == NSERROR_OK) { + browser_window_reformat(bw, false, width, height); + } +} + + +/** + * Release all memory associated with a browser window. + * + * \param bw browser window + */ +static void browser_window_destroy_internal(struct browser_window *bw) +{ + assert(bw); + + NSLOG(netsurf, INFO, "Destroying window"); + + if (bw->children != NULL || bw->iframes != NULL) { + browser_window_destroy_children(bw); + } + + /* Destroy scrollbars */ + if (bw->scroll_x != NULL) { + scrollbar_destroy(bw->scroll_x); + } + + if (bw->scroll_y != NULL) { + scrollbar_destroy(bw->scroll_y); + } + + /* clear any pending callbacks */ + guit->misc->schedule(-1, browser_window_refresh, bw); + /* The ugly cast here is so the reformat function can be + * passed a gui window pointer in its API rather than void* + */ + NSLOG(netsurf, INFO, + "Clearing reformat schedule for browser window %p", bw); + guit->misc->schedule(-1, scheduled_reformat, bw); + + /* If this brower window is not the root window, and has focus, unset + * the root browser window's focus pointer. */ + if (!bw->window) { + struct browser_window *top = browser_window_get_root(bw); + + if (top->focus == bw) + top->focus = top; + + if (top->selection.bw == bw) { + browser_window_set_selection(top, false, false); + } + } + + /* Destruction order is important: we must ensure that the frontend + * destroys any window(s) associated with this browser window before + * we attempt any destructive cleanup. + */ + + if (bw->window) { + /* Only the root window has a GUI window */ + guit->window->destroy(bw->window); + } + + if (bw->loading_content != NULL) { + hlcache_handle_abort(bw->loading_content); + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + } + + if (bw->current_content != NULL) { + content_close(bw->current_content); + hlcache_handle_release(bw->current_content); + bw->current_content = NULL; + } + + if (bw->favicon.loading != NULL) { + hlcache_handle_abort(bw->favicon.loading); + hlcache_handle_release(bw->favicon.loading); + bw->favicon.loading = NULL; + } + + if (bw->favicon.current != NULL) { + content_close(bw->favicon.current); + hlcache_handle_release(bw->favicon.current); + bw->favicon.current = NULL; + } + + if (bw->box != NULL) { + bw->box->iframe = NULL; + bw->box = NULL; + } + + if (bw->jsctx != NULL) { + js_destroycontext(bw->jsctx); + } + + /* These simply free memory, so are safe here */ + + if (bw->frag_id != NULL) { + lwc_string_unref(bw->frag_id); + } + + browser_window_history_destroy(bw); + + free(bw->name); + free(bw->status.text); + bw->status.text = NULL; + NSLOG(netsurf, INFO, "Status text cache match:miss %d:%d", + bw->status.match, bw->status.miss); +} + +/** + * Update URL bar for a given browser window to given URL + * + * \param bw Browser window to update URL bar for. + * \param url URL for content displayed by bw including any fragment. + */ +static inline nserror +browser_window_refresh_url_bar_internal(struct browser_window *bw, nsurl *url) +{ + assert(bw); + assert(url); + + if ((bw->parent != NULL) || (bw->window == NULL)) { + /* Not root window or no gui window so do not set a URL */ + return NSERROR_OK; + } + + return guit->window->set_url(bw->window, url); +} + + +/* exported interface, documented in netsurf/browser_window.h */ +void browser_window_destroy(struct browser_window *bw) +{ + /* can't destoy child windows on their own */ + assert(!bw->parent); + + /* destroy */ + browser_window_destroy_internal(bw); + free(bw); +} + +/* exported interface, documented in netsurf/browser_window.h */ +nserror browser_window_refresh_url_bar(struct browser_window *bw) +{ + nserror ret; + nsurl *display_url; + + assert(bw); + + if (bw->parent != NULL) { + /* Not root window; don't set a URL in GUI URL bar */ + return NSERROR_OK; + } + + if (bw->current_content == NULL) { + /* no content so return about:blank */ + ret = browser_window_refresh_url_bar_internal(bw, + corestring_nsurl_about_blank); + } else if (bw->frag_id == NULL) { + ret = browser_window_refresh_url_bar_internal(bw, + hlcache_handle_get_url(bw->current_content)); + } else { + /* Combine URL and Fragment */ + ret = nsurl_refragment( + hlcache_handle_get_url(bw->current_content), + bw->frag_id, &display_url); + if (ret == NSERROR_OK) { + ret = browser_window_refresh_url_bar_internal(bw, + display_url); + nsurl_unref(display_url); + } + } + + return ret; +} + + +/* exported interface documented in netsurf/browser_window.h */ +nserror +browser_window_navigate(struct browser_window *bw, + nsurl *url, + nsurl *referrer, + enum browser_window_nav_flags flags, + char *post_urlenc, + struct fetch_multipart_data *post_multipart, + hlcache_handle *parent) +{ + hlcache_handle *c; + int depth = 0; + struct browser_window *cur; + uint32_t fetch_flags = 0; + bool fetch_is_post = (post_urlenc != NULL || post_multipart != NULL); + llcache_post_data post; + hlcache_child_context child; + nserror error; + + assert(bw); + assert(url); + + NSLOG(netsurf, INFO, "bw %p, url %s", bw, nsurl_access(url)); + + /* If we're navigating and we have a history entry and a content + * then update the history entry before we navigate to save our + * current state. However since history navigation pre-moves + * the history state, we ensure that we only do this if we've not + * been suppressed. In the suppressed case, the history code + * updates the history itself before navigating. + */ + if (bw->current_content != NULL && + bw->history != NULL && + bw->history->current != NULL && + !(flags & BW_NAVIGATE_NO_TERMINAL_HISTORY_UPDATE)) { + browser_window_history_update(bw, bw->current_content); + } + + /* don't allow massively nested framesets */ + for (cur = bw; cur->parent; cur = cur->parent) { + depth++; + } + if (depth > FRAME_DEPTH) { + NSLOG(netsurf, INFO, "frame depth too high."); + return NSERROR_FRAME_DEPTH; + } + + /* Set up retrieval parameters */ + if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { + fetch_flags |= LLCACHE_RETRIEVE_VERIFIABLE; + } + + if (post_multipart != NULL) { + post.type = LLCACHE_POST_MULTIPART; + post.data.multipart = post_multipart; + } else if (post_urlenc != NULL) { + post.type = LLCACHE_POST_URL_ENCODED; + post.data.urlenc = post_urlenc; + } + + child.charset = content_get_encoding(parent, CONTENT_ENCODING_NORMAL); + if ((parent != NULL) && (content_get_type(parent) == CONTENT_HTML)) { + child.quirks = content_get_quirks(parent); + } + + url = nsurl_ref(url); + + if (referrer != NULL) { + referrer = nsurl_ref(referrer); + } + + /* Get download out of the way */ + if ((flags & BW_NAVIGATE_DOWNLOAD) != 0) { + error = browser_window_download(bw, + url, + referrer, + fetch_flags, + fetch_is_post, + &post); + nsurl_unref(url); + if (referrer != NULL) { + nsurl_unref(referrer); + } + return error; + } + + if (bw->frag_id != NULL) { + lwc_string_unref(bw->frag_id); + } + bw->frag_id = NULL; + + if (nsurl_has_component(url, NSURL_FRAGMENT)) { + bool same_url = false; + + bw->frag_id = nsurl_get_component(url, NSURL_FRAGMENT); + + /* Compare new URL with existing one (ignoring fragments) */ + if ((bw->current_content != NULL) && + (hlcache_handle_get_url(bw->current_content) != NULL)) { + same_url = nsurl_compare(url, + hlcache_handle_get_url( + bw->current_content), + NSURL_COMPLETE); + } + + /* if we're simply moving to another ID on the same page, + * don't bother to fetch, just update the window. + */ + if ((same_url) && + (fetch_is_post == false) && + (nsurl_has_component(url, NSURL_QUERY) == false)) { + nsurl_unref(url); + + if (referrer != NULL) { + nsurl_unref(referrer); + } + + if ((flags & BW_NAVIGATE_HISTORY) != 0) { + browser_window_history_add(bw, + bw->current_content, bw->frag_id); + } + + browser_window_update(bw, false); + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + return NSERROR_OK; + } + } + + browser_window_stop(bw); + browser_window_remove_caret(bw, false); + browser_window_destroy_children(bw); + + NSLOG(netsurf, INFO, "Loading '%s'", nsurl_access(url)); + + browser_window_set_status(bw, messages_get("Loading")); + bw->history_add = (flags & BW_NAVIGATE_HISTORY); + + /* Verifiable fetches may trigger a download */ + if (!(flags & BW_NAVIGATE_UNVERIFIABLE)) { + fetch_flags |= HLCACHE_RETRIEVE_MAY_DOWNLOAD; + } + + error = hlcache_handle_retrieve(url, + fetch_flags | HLCACHE_RETRIEVE_SNIFF_TYPE, + referrer, + fetch_is_post ? &post : NULL, + browser_window_callback, bw, + parent != NULL ? &child : NULL, + CONTENT_ANY, &c); + + switch (error) { + case NSERROR_OK: + bw->loading_content = c; + browser_window_start_throbber(bw); + error = browser_window_refresh_url_bar_internal(bw, url); + break; + + case NSERROR_NO_FETCH_HANDLER: /* no handler for this type */ + /** \todo does this always try and download even + * unverifiable content? + */ + error = guit->misc->launch_url(url); + break; + + default: /* report error to user */ + browser_window_set_status(bw, messages_get_errorcode(error)); + /** @todo should the caller report the error? */ + guit->misc->warning(messages_get_errorcode(error), NULL); + break; + + } + + nsurl_unref(url); + if (referrer != NULL) { + nsurl_unref(referrer); + } + + /* Record time */ + nsu_getmonotonic_ms(&bw->last_action); + + return error; +} + + +/* Exported interface, documented in browser.h */ +bool browser_window_up_available(struct browser_window *bw) +{ + bool result = false; + + if (bw != NULL && bw->current_content != NULL) { + nsurl *parent; + nserror err = nsurl_parent(hlcache_handle_get_url( + bw->current_content), &parent); + if (err == NSERROR_OK) { + result = nsurl_compare(hlcache_handle_get_url( + bw->current_content), parent, + NSURL_COMPLETE) == false; + nsurl_unref(parent); + } + } + + return result; +} + + +/* Exported interface, documented in browser.h */ +nserror browser_window_navigate_up(struct browser_window *bw, bool new_window) +{ + nsurl *current, *parent; + nserror err; + + if (bw == NULL) + return NSERROR_BAD_PARAMETER; + + current = browser_window_access_url(bw); + + err = nsurl_parent(current, &parent); + if (err != NSERROR_OK) { + return err; + } + + if (nsurl_compare(current, parent, NSURL_COMPLETE) == true) { + /* Can't go up to parent from here */ + nsurl_unref(parent); + return NSERROR_OK; + } + + if (new_window) { + err = browser_window_create(BW_CREATE_CLONE, + parent, NULL, bw, NULL); + } else { + err = browser_window_navigate(bw, parent, NULL, + BW_NAVIGATE_HISTORY, NULL, NULL, NULL); + } + + nsurl_unref(parent); + return err; +} + + +/* Exported interface, documented in include/netsurf/browser_window.h */ +nsurl* browser_window_access_url(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content != NULL) { + return hlcache_handle_get_url(bw->current_content); + + } else if (bw->loading_content != NULL) { + /* TODO: should we return this? */ + return hlcache_handle_get_url(bw->loading_content); + } + + return corestring_nsurl_about_blank; +} + +/* Exported interface, documented in include/netsurf/browser_window.h */ +nserror browser_window_get_url( + struct browser_window *bw, + bool fragment, + nsurl** url_out) +{ + nserror err; + nsurl *url; + + assert(bw != NULL); + + if (!fragment || bw->frag_id == NULL || bw->loading_content != NULL) { + /* If there's a loading content, then the bw->frag_id will have + * been trampled, possibly with a new frag_id, but we will + * still be returning the current URL, so in this edge case + * we just drop any fragment. */ + url = nsurl_ref(browser_window_access_url(bw)); + + } else { + err = nsurl_refragment(browser_window_access_url(bw), + bw->frag_id, &url); + if (err != NSERROR_OK) { + return err; + } + } + + *url_out = url; + return NSERROR_OK; +} + +/* Exported interface, documented in browser.h */ +const char* browser_window_get_title(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content != NULL) { + return content_get_title(bw->current_content); + } + + /* no content so return about:blank */ + return nsurl_access(corestring_nsurl_about_blank); +} + +/* Exported interface, documented in browser.h */ +struct history * browser_window_get_history(struct browser_window *bw) +{ + assert(bw != NULL); + + return bw->history; +} + + +/* Exported interface, documented in browser.h */ +bool browser_window_has_content(struct browser_window *bw) +{ + assert(bw != NULL); + + if (bw->current_content == NULL) { + return false; + } + + return true; +} + +/* Exported interface, documented in browser.h */ +struct hlcache_handle *browser_window_get_content(struct browser_window *bw) +{ + return bw->current_content; +} + +/* Exported interface, documented in browser.h */ +nserror browser_window_get_extents(struct browser_window *bw, bool scaled, + int *width, int *height) +{ + assert(bw != NULL); + + if (bw->current_content == NULL) { + *width = 0; + *height = 0; + return NSERROR_BAD_CONTENT; + } + + *width = content_get_width(bw->current_content); + *height = content_get_height(bw->current_content); + + if (scaled) { + *width *= bw->scale; + *height *= bw->scale; + } + + return NSERROR_OK; +} + + +/* exported internal interface, documented in desktop/browser_private.h */ +void browser_window_get_dimensions(struct browser_window *bw, + int *width, int *height, bool scaled) +{ + assert(bw); + + if (bw->window == NULL) { + /* Core managed browser window */ + *width = bw->width; + *height = bw->height; + } else { + /* Front end window */ + guit->window->get_dimensions(bw->window, width, height, scaled); + } +} + + +/* Exported interface, documented in browser.h */ +void browser_window_set_dimensions(struct browser_window *bw, + int width, int height) +{ + assert(bw); + + if (bw->window == NULL) { + /* Core managed browser window */ + bw->width = width; + bw->height = height; + } else { + NSLOG(netsurf, INFO, + "Asked to set dimensions of front end window."); + assert(0); + } +} + + +/** + * scroll to a fragment if present + * + * \param bw browser window + * \return true if the scroll was sucessful + */ +static bool frag_scroll(struct browser_window *bw) +{ + struct rect rect; + + if (bw->frag_id == NULL) { + return false; + } + + if (!html_get_id_offset(bw->current_content, + bw->frag_id, + &rect.x0, + &rect.y0)) { + return false; + } + + rect.x1 = rect.x0; + rect.y1 = rect.y0; + if (browser_window_set_scroll(bw, &rect) == NSERROR_OK) { + if (bw->current_content != NULL && + bw->history != NULL && + bw->history->current != NULL) { + browser_window_history_update(bw, bw->current_content); + } + return true; + } + return false; +} + +/* Exported interface, documented in browser.h */ +void browser_window_update(struct browser_window *bw, bool scroll_to_top) +{ + static const struct rect zrect = { + .x0 = 0, + .y0 = 0, + .x1 = 0, + .y1 = 0 + }; + + if (bw->current_content == NULL) { + return; + } + + switch (bw->browser_window_type) { + + case BROWSER_WINDOW_NORMAL: + /* Root browser window, constituting a front end window/tab */ + guit->window->set_title(bw->window, + content_get_title(bw->current_content)); + + browser_window_update_extent(bw); + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + if (!frag_scroll(bw)) { + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + } + + guit->window->invalidate(bw->window, NULL); + + break; + + case BROWSER_WINDOW_IFRAME: + /* Internal iframe browser window */ + assert(bw->parent != NULL); + assert(bw->parent->current_content != NULL); + + browser_window_update_extent(bw); + + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + frag_scroll(bw); + + html_redraw_a_box(bw->parent->current_content, bw->box); + break; + + case BROWSER_WINDOW_FRAME: + { + struct rect rect; + browser_window_update_extent(bw); + + if (scroll_to_top) { + browser_window_set_scroll(bw, &zrect); + } + + /* if frag_id exists, then try to scroll to it */ + /** @todo don't do this if the user has scrolled */ + frag_scroll(bw); + + rect.x0 = scrollbar_get_offset(bw->scroll_x); + rect.y0 = scrollbar_get_offset(bw->scroll_y); + rect.x1 = rect.x0 + bw->width; + rect.y1 = rect.y0 + bw->height; + + browser_window_update_box(bw, &rect); + } + break; + + default: + case BROWSER_WINDOW_FRAMESET: + /* Nothing to do */ + break; + } +} + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_update_box(struct browser_window *bw, struct rect *rect) +{ + int pos_x; + int pos_y; + struct browser_window *top; + + assert(bw); + + if (bw->window != NULL) { + /* Front end window */ + guit->window->invalidate(bw->window, rect); + } else { + /* Core managed browser window */ + browser_window_get_position(bw, true, &pos_x, &pos_y); + + top = browser_window_get_root(bw); + + rect->x0 += pos_x / bw->scale; + rect->y0 += pos_y / bw->scale; + rect->x1 += pos_x / bw->scale; + rect->y1 += pos_y / bw->scale; + + guit->window->invalidate(top->window, rect); + } +} + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_stop(struct browser_window *bw) +{ + int children, index; + + if (bw->loading_content != NULL) { + hlcache_handle_abort(bw->loading_content); + hlcache_handle_release(bw->loading_content); + bw->loading_content = NULL; + } + + if (bw->current_content != NULL && content_get_status( + bw->current_content) != CONTENT_STATUS_DONE) { + nserror error; + assert(content_get_status(bw->current_content) == + CONTENT_STATUS_READY); + error = hlcache_handle_abort(bw->current_content); + assert(error == NSERROR_OK); + } + + guit->misc->schedule(-1, browser_window_refresh, bw); + + if (bw->children) { + children = bw->rows * bw->cols; + for (index = 0; index < children; index++) + browser_window_stop(&bw->children[index]); + } + if (bw->iframes) { + children = bw->iframe_count; + for (index = 0; index < children; index++) + browser_window_stop(&bw->iframes[index]); + } + + if (bw->current_content != NULL) { + browser_window_refresh_url_bar(bw); + } + + browser_window_stop_throbber(bw); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_reload(struct browser_window *bw, bool all) +{ + hlcache_handle *c; + unsigned int i; + + if (bw->current_content == NULL || bw->loading_content != NULL) + return; + + if (all && content_get_type(bw->current_content) == CONTENT_HTML) { + struct html_stylesheet *sheets; + struct content_html_object *object; + unsigned int count; + + c = bw->current_content; + + /* invalidate objects */ + object = html_get_objects(c, &count); + + for (; object != NULL; object = object->next) { + if (object->content != NULL) + content_invalidate_reuse_data(object->content); + } + + /* invalidate stylesheets */ + sheets = html_get_stylesheets(c, &count); + + for (i = STYLESHEET_START; i != count; i++) { + if (sheets[i].sheet != NULL) { + content_invalidate_reuse_data(sheets[i].sheet); + } + } + } + + content_invalidate_reuse_data(bw->current_content); + + browser_window_navigate(bw, + hlcache_handle_get_url(bw->current_content), + NULL, + BW_NAVIGATE_NONE, + NULL, + NULL, + NULL); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_status(struct browser_window *bw, const char *text) +{ + int text_len; + /* find topmost window */ + while (bw->parent) + bw = bw->parent; + + if ((bw->status.text != NULL) && + (strcmp(text, bw->status.text) == 0)) { + /* status text is unchanged */ + bw->status.match++; + return; + } + + /* status text is changed */ + + text_len = strlen(text); + + if ((bw->status.text == NULL) || (bw->status.text_len < text_len)) { + /* no current string allocation or it is not long enough */ + free(bw->status.text); + bw->status.text = strdup(text); + bw->status.text_len = text_len; + } else { + /* current allocation has enough space */ + memcpy(bw->status.text, text, text_len + 1); + } + + bw->status.miss++; + guit->window->set_status(bw->window, bw->status.text); +} + + +/* Exported interface, documented in netsurf/browser_window.h */ +void browser_window_set_pointer(struct browser_window *bw, + browser_pointer_shape shape) +{ + struct browser_window *root = browser_window_get_root(bw); + gui_pointer_shape gui_shape; + bool loading; + uint64_t ms_now; + + assert(root); + assert(root->window); + + loading = ((bw->loading_content != NULL) || + ((bw->current_content != NULL) && + (content_get_status(bw->current_content) == CONTENT_STATUS_READY))); + + nsu_getmonotonic_ms(&ms_now); + + if (loading && ((ms_now - bw->last_action) < 1000)) { + /* If loading and less than 1 second since last link followed, + * force progress indicator pointer */ + gui_shape = GUI_POINTER_PROGRESS; + + } else if (shape == BROWSER_POINTER_AUTO) { + /* Up to browser window to decide */ + if (loading) { + gui_shape = GUI_POINTER_PROGRESS; + } else { + gui_shape = GUI_POINTER_DEFAULT; + } + + } else { + /* Use what we were told */ + gui_shape = (gui_pointer_shape)shape; + } + + guit->window->set_pointer(root->window, gui_shape); +} + + +/* exported function documented in netsurf/browser_window.h */ +nserror browser_window_schedule_reformat(struct browser_window *bw) +{ + if (bw->window == NULL) { + return NSERROR_BAD_PARAMETER; + } + + guit->misc->schedule(0, scheduled_reformat, bw); + + return NSERROR_OK; +} + + +/* exported function documented in netsurf/browser_window.h */ +void browser_window_reformat(struct browser_window *bw, bool background, + int width, int height) +{ + hlcache_handle *c = bw->current_content; + + if (c == NULL) + return; + + if (bw->browser_window_type != BROWSER_WINDOW_IFRAME) { + /* Iframe dimensions are already scaled in parent's layout */ + width /= bw->scale; + height /= bw->scale; + } + + if (bw->window == NULL) { + /* Core managed browser window; subtract scrollbar width */ + width -= bw->scroll_y ? SCROLLBAR_WIDTH : 0; + height -= bw->scroll_x ? SCROLLBAR_WIDTH : 0; + + width = width > 0 ? width : 0; + height = height > 0 ? height : 0; + } + + content_reformat(c, background, width, height); +} + +/** + * Set browser window scale. + * + * \param bw Browser window. + * \param scale value. + */ +static void browser_window_set_scale_internal(struct browser_window *bw, + float scale) +{ + int i; + hlcache_handle *c; + + if (fabs(bw->scale-scale) < 0.0001) + return; + + bw->scale = scale; + c = bw->current_content; + + if (c != NULL) { + if (content_can_reformat(c) == false) { + browser_window_update(bw, false); + } else { + browser_window_schedule_reformat(bw); + } + } + + for (i = 0; i < (bw->cols * bw->rows); i++) + browser_window_set_scale_internal(&bw->children[i], scale); + for (i = 0; i < bw->iframe_count; i++) + browser_window_set_scale_internal(&bw->iframes[i], scale); +} + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_set_scale(struct browser_window *bw, float scale, bool all) +{ + while (bw->parent && all) + bw = bw->parent; + + browser_window_set_scale_internal(bw, scale); + + if (bw->parent) + bw = bw->parent; + + browser_window_recalculate_frameset(bw); +} + + +/* exported interface documented in netsurf/browser_window.h */ +float browser_window_get_scale(struct browser_window *bw) +{ + if (bw == NULL) { + return 1.0; + } + + return bw->scale; +} + +/** + * Find browser window. + * + * \param bw Browser window. + * \param target Name of target. + * \param depth Depth to scan. + * \param page The browser window page. + * \param rdepth The rdepth. + * \param bw_target the output browser window. + */ +static void browser_window_find_target_internal(struct browser_window *bw, + const char *target, int depth, struct browser_window *page, + int *rdepth, struct browser_window **bw_target) +{ + int i; + + if ((bw->name) && (!strcasecmp(bw->name, target))) { + if ((bw == page) || (depth > *rdepth)) { + *rdepth = depth; + *bw_target = bw; + } + } + + if ((!bw->children) && (!bw->iframes)) + return; + + depth++; + + if (bw->children != NULL) { + for (i = 0; i < (bw->cols * bw->rows); i++) { + if ((bw->children[i].name) && + (!strcasecmp(bw->children[i].name, + target))) { + if ((page == &bw->children[i]) || + (depth > *rdepth)) { + *rdepth = depth; + *bw_target = &bw->children[i]; + } + } + if (bw->children[i].children) + browser_window_find_target_internal( + &bw->children[i], + target, depth, page, + rdepth, bw_target); + } + } + + if (bw->iframes != NULL) { + for (i = 0; i < bw->iframe_count; i++) + browser_window_find_target_internal(&bw->iframes[i], + target, depth, page, rdepth, bw_target); + } +} + + +/* exported interface documented in netsurf/browser_window.h */ +struct browser_window *browser_window_find_target(struct browser_window *bw, + const char *target, browser_mouse_state mouse) +{ + struct browser_window *bw_target; + struct browser_window *top; + hlcache_handle *c; + int rdepth; + nserror error; + + /* use the base target if we don't have one */ + c = bw->current_content; + if (target == NULL && c != NULL && content_get_type(c) == CONTENT_HTML) + target = html_get_base_target(c); + if (target == NULL) + target = TARGET_SELF; + + /* allow the simple case of target="_blank" to be ignored if requested + */ + if ((!(mouse & BROWSER_MOUSE_CLICK_2)) && + (!((mouse & BROWSER_MOUSE_CLICK_2) && + (mouse & BROWSER_MOUSE_MOD_2))) && + (!nsoption_bool(target_blank))) { + /* not a mouse button 2 click + * not a mouse button 1 click with ctrl pressed + * configured to ignore target="_blank" */ + if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank"))) + return bw; + } + + /* handle reserved keywords */ + if (((nsoption_bool(button_2_tab)) && + (mouse & BROWSER_MOUSE_CLICK_2))|| + ((!nsoption_bool(button_2_tab)) && + ((mouse & BROWSER_MOUSE_CLICK_1) && + (mouse & BROWSER_MOUSE_MOD_2))) || + ((nsoption_bool(button_2_tab)) && + ((target == TARGET_BLANK) || + (!strcasecmp(target, "_blank"))))) { + /* open in new tab if: + * - button_2 opens in new tab and button_2 was pressed + * OR + * - button_2 doesn't open in new tabs and button_1 was + * pressed with ctrl held + * OR + * - button_2 opens in new tab and the link target is "_blank" + */ + error = browser_window_create(BW_CREATE_TAB | + BW_CREATE_HISTORY | + BW_CREATE_CLONE, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + return bw_target; + } else if (((!nsoption_bool(button_2_tab)) && + (mouse & BROWSER_MOUSE_CLICK_2)) || + ((nsoption_bool(button_2_tab)) && + ((mouse & BROWSER_MOUSE_CLICK_1) && + (mouse & BROWSER_MOUSE_MOD_2))) || + ((!nsoption_bool(button_2_tab)) && + ((target == TARGET_BLANK) || + (!strcasecmp(target, "_blank"))))) { + /* open in new window if: + * - button_2 doesn't open in new tabs and button_2 was pressed + * OR + * - button_2 opens in new tab and button_1 was pressed with + * ctrl held + * OR + * - button_2 doesn't open in new tabs and the link target is + * "_blank" + */ + error = browser_window_create(BW_CREATE_HISTORY | + BW_CREATE_CLONE, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + return bw_target; + } else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) { + return bw; + } else if ((target == TARGET_PARENT) || + (!strcasecmp(target, "_parent"))) { + if (bw->parent) + return bw->parent; + return bw; + } else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) { + while (bw->parent) + bw = bw->parent; + return bw; + } + + /* find frame according to B.8, ie using the following priorities: + * + * 1) current frame + * 2) closest to front + */ + rdepth = -1; + bw_target = NULL; + for (top = bw; top->parent; top = top->parent); + browser_window_find_target_internal(top, target, 0, bw, &rdepth, + &bw_target); + if (bw_target) + return bw_target; + + /* we require a new window using the target name */ + if (!nsoption_bool(target_blank)) + return bw; + + error = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY, + NULL, + NULL, + bw, + &bw_target); + if (error != NSERROR_OK) { + return bw; + } + + /* frame names should begin with an alphabetic character (a-z,A-Z), + * however in practice you get things such as '_new' and '2left'. The + * only real effect this has is when giving out names as it can be + * assumed that an author intended '_new' to create a new nameless + * window (ie '_blank') whereas in the case of '2left' the intention + * was for a new named window. As such we merely special case windows + * that begin with an underscore. */ + if (target[0] != '_') { + bw_target->name = strdup(target); + if (!bw_target->name) + guit->misc->warning("NoMemory", NULL); + } + return bw_target; +} + + +/** + * Handles the end of a drag operation in a browser window. + * + * \param bw browser window + * \param mouse state of mouse buttons and modifier keys + * \param x coordinate of mouse + * \param y coordinate of mouse + * + * \todo Remove this function, once these things are associated with content, + * rather than bw. + */ +static void browser_window_mouse_drag_end(struct browser_window *bw, + browser_mouse_state mouse, int x, int y) +{ + int scr_x, scr_y; + + switch (bw->drag.type) { + case DRAGGING_SELECTION: + case DRAGGING_OTHER: + case DRAGGING_CONTENT_SCROLLBAR: + /* Drag handled by content handler */ + break; + + case DRAGGING_SCR_X: + + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + scrollbar_mouse_drag_end(bw->scroll_x, mouse, scr_x, scr_y); + + bw->drag.type = DRAGGING_NONE; + break; + + case DRAGGING_SCR_Y: + + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + scrollbar_mouse_drag_end(bw->scroll_y, mouse, scr_x, scr_y); + + bw->drag.type = DRAGGING_NONE; + break; + + default: + browser_window_set_drag_type(bw, DRAGGING_NONE, NULL); + break; + } +} + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_mouse_track(struct browser_window *bw, + browser_mouse_state mouse, int x, int y) +{ + hlcache_handle *c = bw->current_content; + const char *status = NULL; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + + if (bw->window != NULL && bw->drag.window && bw != bw->drag.window) { + /* This is the root browser window and there's an active drag + * in a sub window. + * Pass the mouse action straight on to that bw. */ + struct browser_window *drag_bw = bw->drag.window; + int off_x = 0; + int off_y = 0; + + browser_window_get_position(drag_bw, true, &off_x, &off_y); + + if (drag_bw->browser_window_type == BROWSER_WINDOW_FRAME) { + browser_window_mouse_track(drag_bw, mouse, + x - off_x, y - off_y); + + } else if (drag_bw->browser_window_type == + BROWSER_WINDOW_IFRAME) { + browser_window_mouse_track(drag_bw, mouse, + x - off_x / bw->scale, + y - off_y / bw->scale); + } + return; + } + + if (bw->children) { + /* Browser window has children (frames) */ + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + for (cur_child = 0; cur_child < children; cur_child++) { + + child = &bw->children[cur_child]; + + if (x < child->x || y < child->y || + child->x + child->width < x || + child->y + child->height < y) { + /* Click not in this child */ + continue; + } + + /* It's this child that contains the mouse; pass + * mouse action on to child */ + browser_window_mouse_track(child, mouse, + x - child->x + scrollbar_get_offset( + child->scroll_x), + y - child->y + scrollbar_get_offset( + child->scroll_y)); + + /* Mouse action was for this child, we're done */ + return; + } + + /* Odd if we reached here, but nothing else can use the click + * when there are children. */ + return; + } + + if (c == NULL && bw->drag.type != DRAGGING_FRAME) { + return; + } + + if (bw->drag.type != DRAGGING_NONE && !mouse) { + browser_window_mouse_drag_end(bw, mouse, x, y); + } + + /* Browser window's horizontal scrollbar */ + if (bw->scroll_x != NULL && bw->drag.type != DRAGGING_SCR_Y) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if ((bw->drag.type == DRAGGING_SCR_X) || + (scr_x > 0 && + scr_x < browser_window_get_scrollbar_len(bw, true) && + scr_y > 0 && + scr_y < SCROLLBAR_WIDTH && + bw->drag.type == DRAGGING_NONE)) { + /* Start a scrollbar drag, or continue existing drag */ + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_x, mouse, + scr_x, scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) { + browser_window_set_status(bw, status); + } + + browser_window_set_pointer(bw, pointer); + return; + } + } + + /* Browser window's vertical scrollbar */ + if (bw->scroll_y != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if ((bw->drag.type == DRAGGING_SCR_Y) || + (scr_y > 0 && + scr_y < browser_window_get_scrollbar_len(bw, false) && + scr_x > 0 && + scr_x < SCROLLBAR_WIDTH && + bw->drag.type == DRAGGING_NONE)) { + /* Start a scrollbar drag, or continue existing drag */ + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_y, mouse, + scr_x, scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) { + browser_window_set_status(bw, status); + } + + browser_window_set_pointer(bw, pointer); + return; + } + } + + if (bw->drag.type == DRAGGING_FRAME) { + browser_window_resize_frame(bw, bw->x + x, bw->y + y); + } else if (bw->drag.type == DRAGGING_PAGE_SCROLL) { + /* mouse movement since drag started */ + struct rect rect; + + rect.x0 = bw->drag.start_x - x; + rect.y0 = bw->drag.start_y - y; + + /* new scroll offsets */ + rect.x0 += bw->drag.start_scroll_x; + rect.y0 += bw->drag.start_scroll_y; + + bw->drag.start_scroll_x = rect.x1 = rect.x0; + bw->drag.start_scroll_y = rect.y1 = rect.y0; + + browser_window_set_scroll(bw, &rect); + } else { + assert(c != NULL); + content_mouse_track(c, bw, mouse, x, y); + } +} + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_mouse_click(struct browser_window *bw, + browser_mouse_state mouse, int x, int y) +{ + hlcache_handle *c = bw->current_content; + const char *status = NULL; + browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; + + if (bw->children) { + /* Browser window has children (frames) */ + struct browser_window *child; + int cur_child; + int children = bw->rows * bw->cols; + + for (cur_child = 0; cur_child < children; cur_child++) { + + child = &bw->children[cur_child]; + + if (x < child->x || y < child->y || + child->x + child->width < x || + child->y + child->height < y) { + /* Click not in this child */ + continue; + } + + /* It's this child that contains the click; pass it + * on to child. */ + browser_window_mouse_click(child, mouse, + x - child->x + scrollbar_get_offset( + child->scroll_x), + y - child->y + scrollbar_get_offset( + child->scroll_y)); + + /* Mouse action was for this child, we're done */ + return; + } + + return; + } + + if (!c) + return; + + if (bw->scroll_x != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, true, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if (scr_x > 0 && scr_x < browser_window_get_scrollbar_len(bw, + true) && + scr_y > 0 && scr_y < SCROLLBAR_WIDTH) { + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_x, mouse, + scr_x, scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) + browser_window_set_status(bw, status); + + browser_window_set_pointer(bw, pointer); + return; + } + } + + if (bw->scroll_y != NULL) { + int scr_x, scr_y; + browser_window_get_scrollbar_pos(bw, false, &scr_x, &scr_y); + scr_x = x - scr_x - scrollbar_get_offset(bw->scroll_x); + scr_y = y - scr_y - scrollbar_get_offset(bw->scroll_y); + + if (scr_y > 0 && scr_y < browser_window_get_scrollbar_len(bw, + false) && + scr_x > 0 && scr_x < SCROLLBAR_WIDTH) { + status = scrollbar_mouse_status_to_message( + scrollbar_mouse_action( + bw->scroll_y, mouse, + scr_x, scr_y)); + pointer = BROWSER_POINTER_DEFAULT; + + if (status != NULL) + browser_window_set_status(bw, status); + + browser_window_set_pointer(bw, pointer); + return; + } + } + + switch (content_get_type(c)) { + case CONTENT_HTML: + case CONTENT_TEXTPLAIN: + { + /* Give bw focus */ + struct browser_window *root_bw = browser_window_get_root(bw); + if (bw != root_bw->focus) { + browser_window_remove_caret(bw, false); + browser_window_set_selection(bw, false, true); + root_bw->focus = bw; + } + + /* Pass mouse action to content */ + content_mouse_action(c, bw, mouse, x, y); + } + break; + default: + if (mouse & BROWSER_MOUSE_MOD_2) { + if (mouse & BROWSER_MOUSE_DRAG_2) { + guit->window->drag_save_object(bw->window, c, + GUI_SAVE_OBJECT_NATIVE); + } else if (mouse & BROWSER_MOUSE_DRAG_1) { + guit->window->drag_save_object(bw->window, c, + GUI_SAVE_OBJECT_ORIG); + } + } else if (mouse & (BROWSER_MOUSE_DRAG_1 | + BROWSER_MOUSE_DRAG_2)) { + browser_window_page_drag_start(bw, x, y); + browser_window_set_pointer(bw, BROWSER_POINTER_MOVE); + } + break; + } +} + + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_redraw_rect(struct browser_window *bw, int x, int y, + int width, int height) +{ + content_request_redraw(bw->current_content, x, y, width, height); +} + + +/* exported interface documented in netsurf/browser_window.h */ +void browser_window_page_drag_start(struct browser_window *bw, int x, int y) +{ + assert(bw != NULL); + + browser_window_set_drag_type(bw, DRAGGING_PAGE_SCROLL, NULL); + + bw->drag.start_x = x; + bw->drag.start_y = y; + + if (bw->window != NULL) { + /* Front end window */ + guit->window->get_scroll(bw->window, + &bw->drag.start_scroll_x, + &bw->drag.start_scroll_y); + + guit->window->scroll_start(bw->window); + } else { + /* Core managed browser window */ + bw->drag.start_scroll_x = scrollbar_get_offset(bw->scroll_x); + bw->drag.start_scroll_y = scrollbar_get_offset(bw->scroll_y); + } +} + + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_back_available(struct browser_window *bw) +{ + return (bw && bw->history && + browser_window_history_back_available(bw)); +} + + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_forward_available(struct browser_window *bw) +{ + return (bw && bw->history && + browser_window_history_forward_available(bw)); +} + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_reload_available(struct browser_window *bw) +{ + return (bw && bw->current_content && !bw->loading_content); +} + + +/* exported interface documented in netsurf/browser_window.h */ +bool browser_window_stop_available(struct browser_window *bw) +{ + return (bw && (bw->loading_content || + (bw->current_content && + (content_get_status(bw->current_content) != + CONTENT_STATUS_DONE)))); +} + +/* exported interface documented in browser.h */ +bool browser_window_exec(struct browser_window *bw, const char *src, size_t srclen) +{ + assert(bw != NULL); + + if (!bw->current_content) { + NSLOG(netsurf, DEEPDEBUG, "Unable to exec, no content"); + return false; + } + + if (content_get_status(bw->current_content) != CONTENT_STATUS_DONE) { + NSLOG(netsurf, DEEPDEBUG, "Unable to exec, content not done"); + return false; + } + + /* Okay it should be safe, forward the request through to the content + * itself. Only HTML contents currently support executing code + */ + return content_exec(bw->current_content, src, srclen); +} + +/* exported interface documented in browser_window.h */ +nserror browser_window_console_log(struct browser_window *bw, + browser_window_console_source src, + const char *msg, + size_t msglen, + browser_window_console_flags flags) +{ + browser_window_console_flags log_level = flags & BW_CS_FLAG_LEVEL_MASK; + struct browser_window *root = browser_window_get_root(bw); + + assert(msg != NULL); + /* We don't assert msglen > 0, if someone wants to log a real empty + * string then we won't stop them. It does sometimes happen from + * JavaScript for example. + */ + + /* bw is the target of the log, but root is where we log it */ + + NSLOG(netsurf, DEEPDEBUG, "Logging message in %p targetted at %p", root, bw); + NSLOG(netsurf, DEEPDEBUG, "Log came from %s", + ((src == BW_CS_INPUT) ? "user input" : + (src == BW_CS_SCRIPT_ERROR) ? "script error" : + (src == BW_CS_SCRIPT_CONSOLE) ? "script console" : + "unknown input location")); + + switch (log_level) { + case BW_CS_FLAG_LEVEL_DEBUG: + NSLOG(netsurf, DEBUG, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_LOG: + NSLOG(netsurf, VERBOSE, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_INFO: + NSLOG(netsurf, INFO, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_WARN: + NSLOG(netsurf, WARNING, "%.*s", (int)msglen, msg); + break; + case BW_CS_FLAG_LEVEL_ERROR: + NSLOG(netsurf, ERROR, "%.*s", (int)msglen, msg); + break; + default: + /* Unreachable */ + break; + } + + guit->window->console_log(root->window, src, msg, msglen, flags); + + return NSERROR_OK; +} diff --git a/desktop/local_history.c b/desktop/local_history.c index 75da4aff1..a97741682 100644 --- a/desktop/local_history.c +++ b/desktop/local_history.c @@ -28,6 +28,7 @@ #include "utils/nsurl.h" #include "netsurf/types.h" #include "netsurf/layout.h" +#include "netsurf/browser_window.h" #include "netsurf/core_window.h" #include "netsurf/plotters.h" diff --git a/desktop/search.c b/desktop/search.c index 201d416dd..3d3e7704f 100644 --- a/desktop/search.c +++ b/desktop/search.c @@ -18,11 +18,14 @@ * along with this program. If not, see . */ - /** \file +/** + * \file * Free text search (core) */ #include "content/content.h" +#include "netsurf/types.h" +#include "netsurf/browser_window.h" #include "desktop/browser_private.h" #include "desktop/search.h" diff --git a/desktop/selection.c b/desktop/selection.c index 35eabb2a9..10aa4c7dd 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -36,6 +36,7 @@ #include "html/html_internal.h" #include "html/font.h" #include "text/textplain.h" +#include "netsurf/browser_window.h" #include "netsurf/mouse.h" #include "desktop/browser_private.h" diff --git a/desktop/textinput.c b/desktop/textinput.c index 7fc95f792..660f01a4a 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -36,6 +36,7 @@ #include "netsurf/mouse.h" #include "netsurf/form.h" #include "netsurf/window.h" +#include "netsurf/browser_window.h" #include "netsurf/keypress.h" #include "html/box.h" #include "html/html_internal.h" diff --git a/frontends/amiga/font.c b/frontends/amiga/font.c index e69ff55f0..65e791416 100644 --- a/frontends/amiga/font.c +++ b/frontends/amiga/font.c @@ -24,7 +24,7 @@ #include "utils/log.h" #include "utils/nsoption.h" -#include "netsurf/browser_window.h" +#include "netsurf/browser.h" #include "netsurf/layout.h" #include "amiga/font.h" diff --git a/frontends/framebuffer/font_freetype.c b/frontends/framebuffer/font_freetype.c index 744ac6281..3912821f7 100644 --- a/frontends/framebuffer/font_freetype.c +++ b/frontends/framebuffer/font_freetype.c @@ -29,7 +29,7 @@ #include "utils/nsoption.h" #include "netsurf/utf8.h" #include "netsurf/layout.h" -#include "netsurf/browser_window.h" +#include "netsurf/browser.h" #include "netsurf/plot_style.h" #include "framebuffer/gui.h" diff --git a/frontends/gtk/gui.c b/frontends/gtk/gui.c index 143a1041b..835416860 100644 --- a/frontends/gtk/gui.c +++ b/frontends/gtk/gui.c @@ -41,6 +41,7 @@ #include "netsurf/keypress.h" #include "netsurf/url_db.h" #include "netsurf/cookie_db.h" +#include "netsurf/browser.h" #include "netsurf/browser_window.h" #include "netsurf/misc.h" #include "netsurf/netsurf.h" diff --git a/frontends/windows/main.c b/frontends/windows/main.c index b021751c9..6592e1626 100644 --- a/frontends/windows/main.c +++ b/frontends/windows/main.c @@ -34,6 +34,7 @@ #include "utils/nsoption.h" #include "netsurf/url_db.h" #include "netsurf/cookie_db.h" +#include "netsurf/browser.h" #include "netsurf/browser_window.h" #include "netsurf/fetch.h" #include "netsurf/misc.h" @@ -61,6 +62,26 @@ char **respaths; /** exported global defined in windows/gui.h */ char *nsw32_config_home; /* exported global defined in windows/gui.h */ +/** + * Obtain the DPI of the display. + * + * \return The DPI of the device the window is displayed on. + */ +static int get_screen_dpi(void) +{ + HDC screendc = GetDC(0); + int dpi = GetDeviceCaps(screendc, LOGPIXELSY); + ReleaseDC(0, screendc); + + if (dpi <= 10) { + dpi = 96; /* 96DPI is the default */ + } + + NSLOG(netsurf, INFO, "FIX DPI %d", dpi); + + return dpi; +} + /** * Get the path to the config directory. * @@ -393,6 +414,8 @@ WinMain(HINSTANCE hInstance, HINSTANCE hLastInstance, LPSTR lpcli, int ncmd) return 1; } + browser_set_dpi(get_screen_dpi()); + urldb_load(nsoption_charp(url_file)); urldb_load_cookies(nsoption_charp(cookie_file)); hotlist_init(nsoption_charp(hotlist_path), diff --git a/frontends/windows/window.c b/frontends/windows/window.c index 575d089a3..689e78d8c 100644 --- a/frontends/windows/window.c +++ b/frontends/windows/window.c @@ -71,29 +71,6 @@ static const char windowclassname_main[] = "nswsmainwindow"; static int open_windows = 0; -/** - * Obtain the DPI of the display. - * - * \param hwnd A win32 window handle to get the DPI for - * \return The DPI of the device the window is displayed on. - */ -static int get_window_dpi(HWND hwnd) -{ - HDC hdc = GetDC(hwnd); - int dpi = GetDeviceCaps(hdc, LOGPIXELSY); - - if (dpi <= 10) { - dpi = 96; /* 96DPI is the default */ - } - - ReleaseDC(hwnd, hdc); - - NSLOG(netsurf, INFO, "FIX DPI %d", dpi); - - return dpi; -} - - /** * create and attach accelerator table to main window * @@ -190,8 +167,6 @@ static HWND nsws_window_create(HINSTANCE hInstance, struct gui_window *gw) /* set the gui window associated with this browser */ SetProp(hwnd, TEXT("GuiWnd"), (HANDLE)gw); - browser_set_dpi(get_window_dpi(hwnd)); - if ((nsoption_int(window_width) >= 100) && (nsoption_int(window_height) >= 100) && (nsoption_int(window_x) >= 0) && diff --git a/include/netsurf/browser.h b/include/netsurf/browser.h new file mode 100644 index 000000000..dfe8eb3df --- /dev/null +++ b/include/netsurf/browser.h @@ -0,0 +1,42 @@ +/* + * Copyright 2003 Phil Mellor + * Copyright 2006 James Bursa + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * Browser interfaces. + */ + +#ifndef NETSURF_BROWSER_H_ +#define NETSURF_BROWSER_H_ + +/** + * Set the DPI of the browser. + * + * \param dpi The DPI to set. + */ +nserror browser_set_dpi(int dpi); + +/** + * Get the browser DPI. + * + * \return The DPI in use. + */ +int browser_get_dpi(void); + +#endif diff --git a/include/netsurf/browser_window.h b/include/netsurf/browser_window.h index e701aa3a3..8b5f56c3d 100644 --- a/include/netsurf/browser_window.h +++ b/include/netsurf/browser_window.h @@ -22,8 +22,8 @@ * Browser window creation and manipulation interface. */ -#ifndef _NETSURF_BROWSER_WINDOW_H_ -#define _NETSURF_BROWSER_WINDOW_H_ +#ifndef NETSURF_BROWSER_WINDOW_H_ +#define NETSURF_BROWSER_WINDOW_H_ #include #include @@ -666,19 +666,6 @@ bool browser_window_is_frameset(struct browser_window *bw); nserror browser_window_get_scrollbar_type(struct browser_window *bw, browser_scrolling *h, browser_scrolling *v); -/** - * Set the DPI of the browser. - * - * \param dpi The DPI to set. - */ -nserror browser_set_dpi(int dpi); - -/** - * Get the browser DPI. - * - * \return The DPI in use. - */ -int browser_get_dpi(void); /** * Dump debug info concerning the browser window's contents to file -- cgit v1.2.3